android2harmony 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -407
  2. package/agents/self-tester.md +376 -376
  3. package/dist/index.js +134 -116
  4. package/dist/index.js.map +3 -3
  5. package/package.json +36 -32
  6. package/skills/a2h-resource-convert/SKILL.md +902 -0
  7. package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
  8. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
  9. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
  10. package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
  11. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
  12. package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
  13. package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2166 -0
  14. package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
  15. package/skills/a2h-resource-convert/scripts/package.json +3 -0
  16. package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
  17. package/skills/a2h-ui-transfer/SKILL.md +420 -0
  18. package/skills/a2h-ui-transfer/references/conversion-procedure.md +572 -0
  19. package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
  20. package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
  21. package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +397 -283
  22. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
  23. package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
  24. package/skills/a2h-ui-transfer/scripts/package.json +3 -0
  25. package/skills/hmos-incremental-ui-align/README.md +251 -251
  26. package/skills/hmos-incremental-ui-align/SKILL.md +364 -364
  27. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
  28. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
  29. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
  30. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
  31. package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
  32. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
  33. package/skills/hmos-batch-ui-align/SKILL.md +0 -141
  34. package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
  35. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
  36. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
  37. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
  38. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
  39. package/skills/hmos-resources-convert/SKILL.md +0 -654
  40. package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
  41. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
  42. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  43. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  44. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
  45. package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
  46. package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
  47. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
  48. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
  49. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
  50. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
  51. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
  52. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
  53. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
  54. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
  55. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
  56. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  59. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  60. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
  61. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  62. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
  63. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
  64. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
  65. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
  66. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
  67. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
  68. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
  69. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
  70. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
  71. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
  72. package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
  73. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
  74. package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
  75. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
  76. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
  77. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
  78. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  79. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
  80. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
  81. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  82. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
  83. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
  84. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
  85. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  86. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
  87. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  88. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
  89. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  90. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
  91. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
  92. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
  93. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
  94. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
  95. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
  96. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
  97. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
  98. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
  99. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
  100. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
  101. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
  102. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
  103. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
  104. /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
@@ -0,0 +1,902 @@
1
+ ---
2
+ name: a2h-resource-convert
3
+ description: Convert Android project resources to HarmonyOS project resources. Use this skill when the user wants to migrate, convert, or transfer resource files (strings, colors, dimensions, images, drawables, icons, etc.) from an Android project to a HarmonyOS project. Also trigger when the user mentions Android-to-HarmonyOS migration involving resource files, qualifier directories (like drawable-hdpi, values-zh), or resource format conversion (XML to JSON). Even partial mentions of "Android resources to HarmonyOS" or "migrate Android res" should trigger this skill.
4
+ ---
5
+
6
+ # Android to HarmonyOS Resource Converter
7
+
8
+ This skill converts resource files from an Android project into the resource format used by HarmonyOS projects. It decodes the APK supplied as `apk_path` with the `a2h-resource` npm package (a pure-Node.js resource decoder) to obtain the complete merged resource set, including all library dependencies. If decoding fails, it falls back to converting directly from the project's source `res/` directory — which may be missing library-provided resources, but still produces a useful conversion with clear reporting of what is missing and why. In addition to the standard conversion report, it also produces a dedicated Android resources ↔ HarmonyOS resources mapping markdown document at the user-provided `resource_mapping_path`.
9
+
10
+ It also covers icons that a Jetpack Compose application draws from **code** rather than from `res/` (`androidx.compose.material:material-icons-*`). Those are compiled `ImageVector` objects that never appear in `res/` or `resources.arsc`, so every `res/`-based step below is blind to them; Step 4.1.3 recovers them from `classes*.dex` and synthesizes the matching HarmonyOS media assets.
11
+
12
+ `apk_path` is decoded as given. `harmony_project_dir` is an initialized HarmonyOS project; converted resources are written into it.
13
+
14
+ ## Inputs
15
+
16
+ Declare all inputs up front as explicit `snake_case` variables — do not rely on semantically guessing natural-language phrases:
17
+
18
+ | Variable | Required | Meaning | Typical Phrases |
19
+ |---|---|---|---|
20
+ | `android_project_dir` | yes | Root directory of the Android project (contains `build.gradle` or `build.gradle.kts`) | "Android project path", "安卓项目", "source project" |
21
+ | `harmony_project_dir` | yes | An **initialized** HarmonyOS project. Converted resources are written into it. | "HarmonyOS project path", "鸿蒙项目", "target project" |
22
+ | `resource_mapping_path` | yes | Full output path of the markdown file that records the Android resource inventory and the Android ↔ HarmonyOS mapping details. Write this file in 中文. | "resource mapping path", "资源映射文档" |
23
+ | `apk_path` | yes | Full path of the APK corresponding to `android_project_dir`. It is decoded as given in Step 3 and is the conversion source. | "APK path", "apk文件", "安卓apk" |
24
+ | `material_icon_snapshot_dir` | no | Local copy of `google/material-design-icons`, for environments with no network access. Normally omitted: Step 4.1.3 fetches artwork from a pinned upstream commit. | "图标快照目录", "material icons snapshot" |
25
+
26
+ If any required input cannot be determined from the invocation, ask the user — do not guess. `material_icon_snapshot_dir` is optional: never block on it.
27
+
28
+ ## Toolchain — mandatory
29
+
30
+ **Do not write your own conversion scripts.** This skill ships a canonical implementation; use it.
31
+
32
+ The reason is measured, not stylistic. When this skill was run across 10 applications with agents implementing the conversion themselves, each run produced its own converter — 10 implementations, 3 languages, ~10,000 lines of throwaway code, 3 of them pulling floating-version npm dependencies. The results were not comparable: `converted` counts meant different things per run (file-level in one, entry-level in another), mapping documents ranged from 266 rows to 120,750 for similar inputs, and defect classes appeared in some outputs but not others — 9 of 15 committed patches carried unrenderable `d="@string/…"` path data, one dropped whitespace that Android's quoting syntax deliberately preserves, another lost `clip-path` geometry. Every one of those is a rule stated in `references/`, implemented correctly by some runs and incorrectly by others.
33
+
34
+ Run these four steps in order:
35
+
36
+ ```bash
37
+ # 1. Resources: values, qualifiers, media, XML drawables, adaptive icons, reference resolution
38
+ # <ui_module> below is the UI module resolved from build-profile.json5 (+ the project's
39
+ # architecture doc). It is NOT always `entry` — layered projects use e.g. products/phone.
40
+ node ./scripts/a2h_resource_convert.js \
41
+ --res <decoded_output_path>/res --harmony <harmony_project_dir> \
42
+ --ui-module <ui_module> \
43
+ --records <work_dir>/records.json
44
+
45
+ # 2. Code-defined vector icons — REQUIRED for any Jetpack Compose app (see Step 4.1.3)
46
+ # --fetch pulls artwork from a pinned upstream commit, never a moving branch.
47
+ # Requires network access to raw.githubusercontent.com.
48
+ # Prefer a *minified* (release) APK's decoded dir. On a debug APK the icon library
49
+ # is unshrunk, so the dex lists the whole catalogue (~11k names for
50
+ # material-icons-extended) rather than what the app uses; the script refuses to
51
+ # fetch in that case and tells you to add --attributed-only.
52
+ node ./scripts/code_vector_icons.js \
53
+ --dex <decoded_output_path> --android-src <android_project_dir> \
54
+ --out <harmony_project_dir>/<ui_module>/src/main/resources/base/media \
55
+ --fetch --json <work_dir>/icons.json
56
+
57
+ # 3. Vector fidelity gate — any error blocks completion
58
+ node ./scripts/svg_fidelity_check.js \
59
+ --android-res <decoded_output_path>/res \
60
+ --harmony-media <harmony_project_dir>/<ui_module>/src/main/resources/base/media \
61
+ --json <work_dir>/fidelity.json
62
+
63
+ # 4. Documents, rebuilt from the records of step 1 plus the results of steps 2-3
64
+ node ./scripts/a2h_resource_convert.js --docs-only \
65
+ --records <work_dir>/records.json \
66
+ --icons-json <work_dir>/icons.json --fidelity-json <work_dir>/fidelity.json \
67
+ --report <work_dir>/resource_conversion_report.md \
68
+ --mapping <resource_mapping_path> \
69
+ --apk <apk_path> --android-commit <commit> --app-module <app_module> \
70
+ --harmony <harmony_project_dir>
71
+ ```
72
+
73
+ Step 4 is separate because the documents need the fidelity and icon results, which in turn need the artifacts step 1 produces. It re-reads `records.json` rather than re-running the conversion.
74
+
75
+ **What remains your job:**
76
+
77
+ - resolving the inputs, including the Step 2 application-module decision (the converter cannot make it when several modules apply `com.android.application` — ask the user);
78
+ - reading the tool output and acting on it: a non-zero exit, any fidelity `error`, or any unresolved icon is a finding to address or report, never something to paper over;
79
+ - writing the final Chinese summary for the caller.
80
+
81
+ **What you must not do:** re-implement any rule from `references/` in an ad-hoc script, hand-edit generated resources to make a check pass, or create undocumented placeholder resources.
82
+
83
+ The tables and rules in the rest of this document describe **what the converter implements**. Read them to review its output and to judge edge cases — not as a specification to code against yourself.
84
+
85
+ ## Workflow
86
+
87
+ ### Step 1: Validate Inputs
88
+
89
+ 1. **`apk_path`** — verify the file exists and is readable. If it is missing or unreadable, **stop and ask the user** for the correct path.
90
+
91
+ 2. **`android_project_dir`** — verify it exists and contains `build.gradle` or `build.gradle.kts`. The source `res/` fallback in Step 3 and the library-dependency correlation in Step 5 both read from it.
92
+
93
+ The main resource target directory is `<harmony_project_dir>/<ui_module>/src/main/resources/`, where `<ui_module>` is resolved in Step 2 and passed to the scripts as `--ui-module`. It defaults to `entry` for single-module projects but is a multi-segment path in layered ones (e.g. `products/phone`) — never hardcode `entry`.
94
+
95
+ ### Step 2: Resolve the Application Module
96
+
97
+ Resolve `<app_module>` — the module whose Gradle file applies the `com.android.application` plugin (grep `com.android.application` across `<android_project_dir>/*/build.gradle` and `*/build.gradle.kts`). It is **usually** `app/`, but do not assume that name: many projects use `mobile/`, `client/`, `androidApp/`, or a nested path. Store it relative to `<android_project_dir>` and use it everywhere below instead of a literal `app`. If several modules apply the plugin, ask the user which one corresponds to `apk_path`.
98
+
99
+ `<app_module>` is used by the source `res/` fallback (Step 3 / Step 4) and by the library-dependency correlation in the report. Resolve it now so those steps do not have to re-derive it.
100
+
101
+ Why the decoded APK is the preferred source: it carries all merged resources from application source **and** library dependencies (AARs, Maven artifacts). The source `res/` fallback is missing library resources, so some resource references become unresolvable. The conversion report flags this explicitly.
102
+
103
+ ### Step 3: Decode the APK's resources
104
+
105
+ Decode `apk_path` into `<decoded_output_path>` with the `a2h-resource` decoder.
106
+
107
+ First make sure the `a2h-resource` CLI is installed (published to npm). Install it once, globally:
108
+
109
+ ```bash
110
+ npm install -g a2h-resource
111
+ ```
112
+
113
+ Then run it directly:
114
+
115
+ ```bash
116
+ a2h-resource <apk_path> -o <decoded_output_path>
117
+ ```
118
+
119
+ Alternatively, if you prefer not to install globally, run it on-demand with `npx` (which fetches the package automatically):
120
+
121
+ ```bash
122
+ npx --yes a2h-resource <apk_path> -o <decoded_output_path>
123
+ ```
124
+
125
+ `a2h-resource` is a pure-Node.js resource decoder distributed as an npm package and requires only Node.js `>= 20`. It reads the APK's `resources.arsc` — the fully-merged resource table produced at build time — so its output at `<decoded_output_path>/res/` is the **complete merged resource set** (source + all library/AAR resources merged in at build time). It produces:
126
+
127
+ - `<decoded_output_path>/AndroidManifest.xml` — decoded, id references resolved (`@id/…`, `@string/…`, `@android:…`).
128
+ - `<decoded_output_path>/res/` — qualifier directories with **real resource names** and decoded content:
129
+ - `values*/`: `strings`, `colors`, `dimens`, `bools`, `integers`, `ids`, `<string-array>`/`<integer-array>`/`<array>`, `plurals` (plus `styles`/`attrs`, which this skill logs as unmappable).
130
+ - `drawable*/`, `mipmap*/`, `layout*/`, `menu/`, `xml/`, `color/`: binary XML decoded to text — `<vector>`, `<shape>`, `<selector>`, `<layer-list>`, `<adaptive-icon>`, layouts, menus — ready for the Step 4 conversions.
131
+ - `.9.png`: restored to **source form** (1-px border with stretch/padding markers).
132
+ - `raw/`, `font/`, and other file resources placed under their qualifier dirs.
133
+ - passthrough directories (`assets/`, `lib/`, `classes.dex`, …) copied as-is.
134
+ - `<decoded_output_path>/svg/` — VectorDrawables pre-converted to SVG. **This skill runs its own SVG conversion** per `references/xml-drawable-to-svg-rules.md`, so this directory is supplementary; ignore it and use the decoded XML under `res/drawable*/` as the conversion source.
135
+
136
+ On success, set `resource_source = "decoded_apk"` and use `<decoded_output_path>/res/` as the conversion source.
137
+
138
+ **If decoding fails** (the command errors out — e.g. a rare APK `a2h-resource` cannot parse, or a multi-package/split APK where only the first package is decoded): record the error, then fall back to the project's source `res/` directory at `<android_project_dir>/<app_module>/src/main/res/` (`<app_module>` from Step 2). Set `resource_source = "source_res"` and state in the report that decoding failed, so the developer knows library-merged resources may be missing.
139
+
140
+ ### Step 4: Convert Resources
141
+
142
+ Conversion is performed by `scripts/a2h_resource_convert.js` (Toolchain step 1). It reads the resource source directory, converts every resource type described below, and records an inventory row and a mapping row for each one — including skips, fallbacks, and unmappable cases — into `records.json`.
143
+
144
+ Read `references/conversion-rules.md` and `references/resource-mapping-rules.md` so you can **review** what it produced and reason about edge cases. Do not re-implement them.
145
+
146
+ **Determining the source `res/` directory when the Step 3 decode fallback applies:**
147
+ - Check `<android_project_dir>/<app_module>/src/main/res/` (`<app_module>` = the application module resolved in Step 2; do not hardcode `app`)
148
+ - If multi-module, also check other module directories
149
+ - If the project uses flavor source sets, include those too (e.g., `<app_module>/src/debug/res/`, `<app_module>/src/flavor/res/`)
150
+
151
+ #### 4.1 Resource Type Mapping
152
+
153
+ | Android Resource Dir | HarmonyOS Target | Notes |
154
+ |---|---|---|
155
+ | `drawable/` (images: png, jpg, webp, gif) | `base/media/` | Direct file copy |
156
+ | `drawable/` (nine-patch: `*.9.png`) | `base/media/` as `*_9.png` | Rename `.9.png` → `_9.png`; HarmonyOS does not allow `.` in resource filenames except for the final extension separator. Nine-patch stretching behavior is lost — log in report. |
157
+ | `drawable/` (Android vector drawables: `<vector>`) | `base/media/` as `.svg` | Convert VectorDrawable XML to SVG format |
158
+ | `drawable/` (shape drawables: `<shape>`) | `base/media/` as `.svg` | Convert shape XML to SVG |
159
+ | `drawable/` (layer-list drawables: `<layer-list>`) | `base/media/` as `.svg` | Convert layered drawable to SVG with nested elements |
160
+ | `drawable/` (selector/state-list drawables) | `base/media/` (default state as `.svg`) | Extract default state item and convert to SVG; log state variants in report |
161
+ | `drawable/` (ripple, animated-vector, transition) | No direct equivalent | Log as unmappable; note in report |
162
+ | `mipmap/` (PNG/WEBP images) | `base/media/` | App launcher icons — direct copy; rename `.9.png` → `_9.png` if nine-patch |
163
+ | `mipmap/` (adaptive-icon XML: `<adaptive-icon>`) | `base/media/` as `xxx_layered_image.json` | Convert to layered-image JSON (see below) |
164
+ | `mipmap/` (vector XML: `<vector>`) | `base/media/` as `.svg` | Convert VectorDrawable to SVG |
165
+ | `values/strings.xml` | `base/element/string.json` | XML → JSON conversion |
166
+ | `values/colors.xml` | `base/element/color.json` | XML → JSON; fix color format |
167
+ | `values/dimens.xml` | `base/element/float.json` | XML → JSON; convert units |
168
+ | `values/integers.xml` | `base/element/integer.json` | XML → JSON |
169
+ | `values/bools.xml` | `base/element/boolean.json` | XML → JSON |
170
+ | `values/arrays.xml` (string-array) | `base/element/strarray.json` | XML → JSON |
171
+ | `values/arrays.xml` (integer-array) | `base/element/intarray.json` | XML → JSON |
172
+ | `values/plurals.xml` | `base/element/plural.json` | XML → JSON |
173
+ | `values/styles.xml` | No direct equivalent | Log in report |
174
+ | `values/attrs.xml` | No direct equivalent | Log in report |
175
+ | `raw/` (non-Lottie) | `rawfile/` | Direct file copy |
176
+ | `raw/` (Lottie JSON: `v`+`fr`+`ip`+`op`+`layers` signature) | `rawfile/lottie/<normalized>.json` | Identify by JSON structure signature; normalize filename to lowercase `[a-z0-9_]`. See `references/lottie-conversion-rules.md`. |
177
+ | `assets/` (Lottie JSON: `v`+`fr`+`ip`+`op`+`layers` signature) | `rawfile/lottie/<normalized>.json` | Same identification and normalization rules as `raw/` Lottie. `assets/` is a passthrough dir in APK decoding — scan `<decoded_output_path>/assets/` recursively. |
178
+ | `assets/` (non-Lottie files) | Passthrough | Not part of this skill's default conversion scope — reserved for future rules. Record inventory rows with `mapping_kind = unmappable`. |
179
+ | `font/` | `rawfile/fonts/` | Copy fonts to rawfile |
180
+ | `xml/` | `base/profile/` | Only config-like XML; rename to .json if possible |
181
+ | `anim/`, `animator/` | No direct equivalent | Log in report |
182
+ | `layout/` | No direct equivalent | ArkUI uses declarative UI; log in report |
183
+ | `menu/` | No direct equivalent | Log in report |
184
+ | `color/` (color state lists) | No direct equivalent | Log in report |
185
+
186
+ #### 4.1.1 Adaptive Icon → Layered-Image JSON Conversion
187
+
188
+ Android adaptive-icon XML files (root element `<adaptive-icon>`, commonly found in `mipmap-anydpi-v26/`) are converted to HarmonyOS layered-image JSON files.
189
+
190
+ **Conversion rules:**
191
+ 1. Parse the `<adaptive-icon>` XML and extract the `<background>` and `<foreground>` elements
192
+ 2. Only `background` and `foreground` are valid keys in the output JSON. Skip `monochrome` (and any other elements) **from the JSON output** — HarmonyOS has no layered-image equivalent. However, the resource *referenced* by `monochrome` (e.g., `@drawable/ic_launcher_monochrome`) is a real asset that should still be converted as a normal drawable.
193
+ 3. Convert Android resource references to HarmonyOS format:
194
+ - `@drawable/xxx` → `$media:xxx`
195
+ - `@mipmap/xxx` → `$media:xxx`
196
+ - `@color/xxx` → **generate a solid color PNG** (see rule 4 below)
197
+ 4. **Critical — color background handling**: HarmonyOS `layered-image` only accepts `$media:xxx` references. It does NOT support `$color:xxx`. When the `background` (or `foreground`) references a color (`@color/xxx`):
198
+ a. Look up the **resolved hex value** of `@color/xxx` from the resource source's `res/values*/colors.xml`. Follow reference chains if the color itself references another color — keep resolving until you reach a concrete hex value.
199
+ b. Generate a solid-color PNG filled entirely with that hex color. A minimal valid PNG of any small size (e.g., 1×1 or 108×108 pixels) is sufficient.
200
+ c. Name the PNG `<color_name>_bg.png` (e.g., `md_orange_700_bg.png`) and place it in `base/media/`.
201
+ d. Reference it as `$media:<color_name>_bg` in the layered-image JSON.
202
+ e. **If the color cannot be resolved** (e.g., it comes from a library and we're using source `res/`), log it as an unsatisfied dependency in the report and use a placeholder reference.
203
+ 5. **Critical — inline layer content**: a layer can carry its artwork as a *child element* instead of an `android:drawable` reference (`<foreground><vector …/></foreground>`, common for shortcut icons). Convert that inline drawable to its own media file named `<icon_name>_<layer>.svg`, and reference it as `$media:<icon_name>_<layer>`. Never emit the layered-image JSON with the layer simply absent: the icon then renders with no artwork, and no existence check catches it because the JSON is present and valid. A `<bitmap android:src="@drawable/x"/>` wrapper is just a reference — point at `$media:x`. An inline drawable with no static SVG equivalent becomes an `unmappable` mapping row with the reason stated.
204
+ 6. Output filename: `<original_name>_layered_image.json` (e.g., `ic_launcher.xml` → `ic_launcher_layered_image.json`)
205
+ 7. Store in the target `media/` directory (typically `base/media/`)
206
+
207
+ **Example:**
208
+ ```xml
209
+ <!-- mipmap-anydpi-v26/ic_launcher.xml -->
210
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
211
+ <background android:drawable="@color/md_orange_700" />
212
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
213
+ <monochrome android:drawable="@drawable/ic_launcher_monochrome" />
214
+ </adaptive-icon>
215
+ ```
216
+
217
+ Suppose `@color/md_orange_700` resolves to `#F57C00`. Generate `base/media/md_orange_700_bg.png` (a solid `#F57C00` filled PNG). Then convert to `base/media/ic_launcher_layered_image.json`:
218
+ ```json
219
+ {
220
+ "layered-image": {
221
+ "background": "$media:md_orange_700_bg",
222
+ "foreground": "$media:ic_launcher_foreground"
223
+ }
224
+ }
225
+ ```
226
+
227
+ #### 4.1.2 Lottie Animation JSON Migration
228
+
229
+ Android projects that use `com.airbnb.android:lottie` ship their animations as JSON in `assets/` or `res/raw/`. On HarmonyOS these are consumed by `@ohos/lottie` (installed with `ohpm install @ohos/lottie`) which loads them from `rawfile/` at runtime. This skill migrates the JSON asset and records the host-screen binding — it does NOT generate ArkTS code.
230
+
231
+ **Identification (always required — do not rely on `.json` extension):**
232
+
233
+ A file is a Lottie animation JSON if its parsed root object contains ALL of these fields: `v`, `fr`, `ip`, `op`, `w`, `h`, and `layers` (array). Non-Lottie JSON in `raw/` continues to follow the normal raw-file rule.
234
+
235
+ **Sources scanned:**
236
+
237
+ - `<decoded_output_path>/assets/` (recursive) — assets is a passthrough directory in APK decoding
238
+ - `<decoded_output_path>/res/raw/`
239
+ - Source-fallback equivalents: every module's `src/*/assets/` and `src/*/res/raw/`, including flavor source sets
240
+
241
+ **Target:**
242
+
243
+ `<ui_module>/src/main/resources/rawfile/lottie/<normalized>.json`. The load path recorded for the developer (used later as `lottie.loadAnimation({ path: ... })`) is `lottie/<normalized>.json`, relative to `rawfile/`, no leading slash.
244
+
245
+ **Filename normalization:** lowercase; replace `[^a-z0-9_]` in the base name with `_`; collapse consecutive `_`; trim leading/trailing `_`; if starts with a digit, prefix `lottie_`. Examples:
246
+
247
+ | Original | Normalized |
248
+ |---|---|
249
+ | `AndroidWave.json` | `androidwave.json` |
250
+ | `Lottie Logo 1.json` | `lottie_logo_1.json` |
251
+ | `HamburgerArrow.json` | `hamburgerarrow.json` |
252
+ | `01-intro.json` | `lottie_01_intro.json` |
253
+
254
+ **Filename collision:** if `assets/logo.json` and `res/raw/logo.json` both target `lottie/logo.json`, disambiguate as `lottie/assets_logo.json` and `lottie/raw_logo.json`. Record both in `notes`.
255
+
256
+ **Reference extraction (in addition to the generic `@type/name` scan of `references/dependency-analysis-rules.md`):**
257
+
258
+ XML attribute patterns (namespace-agnostic — match by local name):
259
+
260
+ | Attribute | Points at | Migration action |
261
+ |---|---|---|
262
+ | `lottie_fileName="Xxx.json"` | `assets/Xxx.json` | Migrate the JSON. Attribute the JSON to the Activity/Fragment inflating the layout XML. |
263
+ | `lottie_rawRes="@raw/name"` | `res/raw/name.json` | Same. |
264
+ | `lottie_url="https://..."` | Runtime URL | No local file — record with `mapping_kind = remote resource (no local target)` and `source_category = 运行时远程资源`. |
265
+ | `lottie_fallbackRes="@drawable/xxx"` | Fallback drawable | Not a Lottie asset; use the normal drawable rules, but link it to the Lottie row in `notes`. |
266
+
267
+ Code patterns (grep across `.kt` / `.java`):
268
+
269
+ - Literal string arg to `LottieCompositionFactory.fromAsset(...)` / `.fromRawRes(...)` / `.fromJsonInputStream(...)` / `.fromUrl(...)` or `LottieAnimationView.setAnimation(...)`.
270
+ - `AssetManager.list(...)` results filtered by `.json` and dispatched to a factory method — this is the "dynamic picker" pattern. In this case attribute every listed JSON to the **detail screen** that the picker's click handler navigates to (via `startActivity`), not the picker itself. (See the airbnb `lottie-android` sample: `PreviewFragment` picker → `PlayerActivity` detail.)
271
+ - Data classes carrying `assetName` / `fileUri` / `url` consumed by a factory method — chase to the actual factory call site to classify local vs remote.
272
+
273
+ **Host-screen binding (required for every Lottie row):**
274
+
275
+ Attribute the JSON to the Activity/Fragment that plays it. Evidence sources, in priority order:
276
+
277
+ 1. XML host layout → the Activity/Fragment that inflates it (`setContentView(R.layout.<name>)`, `AppCompatActivity(R.layout.<name>)` constructor form, `Fragment(R.layout.<name>)`, ViewBinding/DataBinding class name derived from the layout).
278
+ 2. Code-loaded (literal): the enclosing class. If it is a ViewModel/repository, walk one hop up to its observing Fragment/Activity.
279
+ 3. Code-loaded (dynamic picker): the navigation target of the picker's click handler, not the picker itself.
280
+ 4. Manifest / Application / splash: `Global` or `Launcher`.
281
+ 5. No evidence: `Unknown` (explain in notes).
282
+
283
+ Then propose a HarmonyOS page name: strip the `Activity` / `Fragment` suffix and append `Page` (e.g., `PlayerActivity` → `PlayerPage`). Collapse Activity+Fragment pairs of the same screen to one page. `Launcher` → `EntryAbility`, `Global` → `App`, `Unknown` → `Unknown`. Mark this as a suggestion in notes: "HarmonyOS page name inferred from Android host; verify against actual ArkUI pages".
284
+
285
+ **Embedded image assets in a Lottie JSON:** if the JSON declares an `images` array pointing at external files, copy each referenced image to `rawfile/lottie/images/<relative_path>` preserving the JSON's `imagePath` layout, and record each image as its own inventory + mapping row.
286
+
287
+ **Read `references/lottie-conversion-rules.md` for the full, detailed rules** (identification thresholds, edge cases like dead assets and invalid JSON, mapping-record schema).
288
+
289
+ #### 4.1.3 Code-Defined Vector Icons (Jetpack Compose)
290
+
291
+ Jetpack Compose applications take most of their iconography from `androidx.compose.material:material-icons-core` / `-extended`. Those icons are Kotlin-generated `ImageVector` objects compiled into `classes*.dex`. They are **not** in `res/drawable*/` and **not** in `resources.arsc`, so they are absent from the decoded `res/` tree that every other step of this skill reads.
292
+
293
+ This blind spot fails silently and must be handled explicitly: Step 5.3's layout/menu scan finds nothing (a Compose app has no layout XML) and Step 6.8's `$r('app.media.…')` cross-check finds nothing (the HarmonyOS shell has no such references yet), so both gates report success while the migrated page renders no icons at all. On a real Compose app, 9 of the 10 icons on one screen were code-defined and produced no output and no warning.
294
+
295
+ **Run this analysis when any of these hold** — otherwise record `Code-defined vector icons: none detected` and skip:
296
+ - `build.gradle` / `build.gradle.kts` / `gradle/libs.versions.toml` declares `material-icons-core` or `material-icons-extended`
297
+ - any `.kt` file imports `androidx.compose.material.icons.`
298
+ - `classes*.dex` contains icon name strings (the checker below detects this on its own)
299
+
300
+ Three questions need three different sources — do not try to answer them all from one place:
301
+
302
+ | Question | Source | Needs `android_project_dir`? |
303
+ |---|---|---|
304
+ | **Which** icons the app uses | `classes*.dex` string pool | No |
305
+ | **What** each looks like | Pinned upstream Material Icons snapshot | No |
306
+ | **Where** each is used (screen attribution) | Android source `.kt` files | **Yes** |
307
+
308
+ The dex is authoritative for scope — it includes icons used by library code and excludes icons R8 removed as dead code — but it cannot give attribution, because R8 obfuscates the icon package and leaves only a flat, sorted string pool. Attribution comes from the one readable line in the Kotlin source (`icon = Icons.Outlined.GetApp`). `android_project_dir` is already a required input, so this adds no new dependency.
309
+
310
+ Run the bundled helper:
311
+
312
+ ```
313
+ node ./scripts/code_vector_icons.js --dex <decoded_output_path> \
314
+ --android-src <android_project_dir> \
315
+ --out <harmony_project_dir>/<ui_module>/src/main/resources/base/media \
316
+ --snapshot <material_icon_snapshot_dir> \
317
+ --json <harmony_project_dir>/code_vector_icons.json
318
+ ```
319
+
320
+ **`--android-src` is required, not decorative, and a debug APK needs `--attributed-only`.** The dex string pool is a *superset* of what the app uses: it contains whatever the icon library shipped. R8 narrows it to the real set, but `isMinifyEnabled` is typically only on for `release`, so a debug APK's dex lists the entire catalogue. `--android-src` is what lets the script attribute names to actual source references and tell the two apart; without `--attributed-only` on an unshrunk dex it would fetch and write every catalogue entry (thousands of files, tens of minutes, and a polluted `base/media` that must then be cleaned by hand). The script detects this and refuses to fetch, naming the two ways out.
321
+
322
+ `--fetch` is the normal mode: it pulls artwork from a pinned upstream commit — never a moving branch — and therefore needs network access to `raw.githubusercontent.com`. Use `--snapshot <dir>` instead only on network-isolated machines. Exit code 1 means some references could not be resolved; those MUST be recorded as `unmappable` rows, never dropped.
323
+
324
+ Targets are named `base/media/ic_<material_name>_<style>.svg`. The style suffix is mandatory: in the classic Material Icons set `Outlined.X` is sometimes different artwork from `Filled.X` (`bookmark`) and sometimes byte-identical (`favorite`), so naming by bare `ic_<name>.svg` silently drops one of the pair. `AutoMirrored` is **not** part of the filename — HarmonyOS qualifier directories have no right-to-left dimension, so mirroring is handled at the ArkUI component level and recorded in `notes`.
325
+
326
+ **Read `references/code-vector-icon-rules.md` for the full rules** (extraction patterns, the exact name transform, the `symbols`-category trap in the upstream index, SVG post-processing, collision handling with `res/`-derived media, and the fallback table for unresolvable or app-defined `ImageVector`s).
327
+
328
+ #### 4.2 Qualifier Directory Mapping
329
+
330
+ Android uses qualifiers as directory suffixes (e.g., `drawable-hdpi`, `values-ar`). HarmonyOS uses a different qualifier directory naming convention. Convert as follows:
331
+
332
+ **Screen density mapping** (Android DPI names differ from HarmonyOS):
333
+
334
+ | Android Qualifier | Android DPI | HarmonyOS Qualifier | HarmonyOS DPI Range |
335
+ |---|---|---|---|
336
+ | `ldpi` | ~120 | `sdpi` | (0, 120] |
337
+ | `mdpi` | ~160 | `mdpi` | (120, 160] |
338
+ | `hdpi` | ~240 | `ldpi` | (160, 240] |
339
+ | `xhdpi` | ~320 | `xldpi` | (240, 320] |
340
+ | `xxhdpi` | ~480 | `xxldpi` | (320, 480] |
341
+ | `xxxhdpi` | ~640 | `xxxldpi` | (480, 640] |
342
+ | `nodpi` | N/A | `base` | No density qualifier; use base |
343
+ | `anydpi` | N/A | `base` | No density qualifier; use base |
344
+
345
+ **Language/region mapping**:
346
+ - `values-ar` → `ar/element/`
347
+ - `values-zh-rCN` or `values-zh` → `zh_CN/element/` or `zh/element/`
348
+ - `values-en-rUS` → `en_US/element/`
349
+ - `drawable-ar` → `ar/media/`
350
+
351
+ The pattern: strip the base type prefix (e.g., `values-`, `drawable-`), convert the qualifier to HarmonyOS format, then place resources under the appropriate resource group directory (`element/`, `media/`, or `profile/`).
352
+
353
+ **Multi-qualifier directories**: Android directories can combine multiple qualifiers with hyphens (e.g., `mipmap-anydpi-v26`, `drawable-night-v21`). Parse ALL qualifiers individually, map each one, strip unsupported ones, and use whatever valid qualifiers remain. Never reject a directory just because one of its qualifiers is unsupported — strip it and keep going. If all qualifiers are stripped, use `base/`.
354
+
355
+ **Qualifiers to strip** (only these are silently removed):
356
+ - API level `v<N>` (e.g., `v26`, `v21`)
357
+
358
+ **Qualifiers without a HarmonyOS equivalent** (skip entirely):
359
+ - Smallest width `sw<N>dp` (e.g., `sw600dp`)
360
+ - Available width `w<N>dp` (e.g., `w480dp`)
361
+ - Available height `h<N>dp`
362
+ - Screen size (`small`, `normal`, `large`, `xlarge`)
363
+
364
+ These qualifiers have no HarmonyOS mapping. When a directory's only remaining qualifiers (after stripping API level etc.) are from this list, **skip all resources in that directory** and mark them as "unmapped" in the conversion report with reason "Unsupported qualifier: no HarmonyOS equivalent". Do NOT convert them to `base/` or preserve the qualifier as-is — skipping ensures correct conversion output.
365
+
366
+ **Common example**: `mipmap-anydpi-v26` → qualifiers: `anydpi` (→ `base`) + `v26` (→ strip) → target: `base/media/`. The files inside (typically `<adaptive-icon>` XML) should be processed normally by reading their XML content to determine the type — adaptive-icon XML gets converted to layered-image JSON.
367
+
368
+ **HarmonyOS qualifier directory naming rules**:
369
+ - Language and region are joined by underscore: `zh_CN`, `en_US`
370
+ - Multiple qualifier types are separated by hyphens: `zh_CN-dark-ldpi`
371
+ - Order: MCC_MNC-language_script_country/region-orientation-device-colormode-density
372
+
373
+ **Orientation mapping**:
374
+ - `land` → `horizontal`
375
+ - `port` → `vertical`
376
+
377
+ **Night mode mapping**:
378
+ - `night` → `dark`
379
+ - `notnight` → `light`
380
+
381
+ **Handling values with non-mappable qualifiers** (e.g., `values-sw600dp`, `values-w480dp`, `values-w600dp`):
382
+ These directories contain value resources qualified by screen-size constraints that HarmonyOS does not support. **Skip all resources in these directories** and mark them as "unmapped" in the conversion report with reason "Unsupported qualifier: `sw600dp` / `w480dp` / etc. — no HarmonyOS equivalent". Do NOT convert them to `base/element/` or preserve the qualifier as-is.
383
+
384
+ #### 4.2.1 Record Resource Mapping Metadata
385
+
386
+ As each Android resource is processed, maintain two parallel data sets for the final mapping document:
387
+
388
+ 1. **Android resource inventory records** — one record per Android resource item
389
+ 2. **Android ↔ HarmonyOS mapping records** — one record per conversion or non-conversion outcome
390
+
391
+ Use the rules in `references/resource-mapping-rules.md`.
392
+
393
+ **Minimum fields for each Android resource inventory record:**
394
+ - Android resource path
395
+ - Android resource name
396
+ - Function description
397
+ - Screen(s)
398
+ - Source category
399
+ - Type category
400
+ - Status
401
+ - Notes / evidence
402
+
403
+ **Minimum fields for each mapping record:**
404
+ - Android resource path
405
+ - Android screen(s)
406
+ - Android source category
407
+ - Android type category
408
+ - HarmonyOS target path (or `N/A`)
409
+ - Mapping kind
410
+ - Notes
411
+
412
+ **Recording granularity:**
413
+ - File resources (`drawable`, `mipmap`, `xml`, `font`, `raw`) → record at file level
414
+ - `values/*.xml` resources → record at entry level using `file_path#tag/name` notation, e.g. `app/src/main/res/values/strings.xml#string/app_name`
415
+ - One-to-many conversions (for example adaptive icon XML producing layered-image JSON plus generated PNG assets) → record each target as its own mapping row
416
+ - Many-to-one conversions (for example multiple Android value entries ending up in one HarmonyOS JSON file) → still record each Android source entry separately
417
+ - Unmappable resources, system resources, third-party-library-only resources, and runtime remote resources must also be recorded; never silently omit them from the mapping metadata
418
+ #### 4.3 Value Format Conversions
419
+
420
+ When converting `values/*.xml` to HarmonyOS JSON format:
421
+
422
+ **Colors**:
423
+ - Android `#RRGGBB` → HarmonyOS `#ffRRGGBB` (prepend `ff` for full opacity)
424
+ - Android `#AARRGGBB` → HarmonyOS `#AARRGGBB` (already in correct format, HarmonyOS uses same order)
425
+ - Android `#RGB` → expand to `#ffRRGGBB`
426
+ - Android `#ARGB` → expand to `#AARRGGBB`
427
+
428
+ **Dimensions** (for `dimens.xml` → `float.json`):
429
+ - `dp` → `vp` (virtual pixels, similar concept)
430
+ - `sp` → `fp` (font pixels)
431
+ - `px` → keep as `px`
432
+ - Plain numbers → append `vp`
433
+
434
+ **Strings**:
435
+ - XML entities (`&amp;`, `&lt;`, `&gt;`, `&quot;`, `&apos;`) → decoded characters
436
+ - `\n`, `\t` → preserved
437
+ - CDATA sections → extract text content
438
+ - String format placeholders (`%1$s`, `%2$d`) → preserved (HarmonyOS uses same format)
439
+
440
+ **Booleans**:
441
+ - `"true"` / `"false"` → `true` / `false` (as JSON booleans)
442
+
443
+ **Integers**:
444
+ - Integer values from `integers.xml` MUST be stored as JSON number type, NOT as JSON strings
445
+ - `<integer name="max_lines">3</integer>` → `{"name": "max_lines", "value": 3}` (JSON number `3`, NOT string `"3"`)
446
+ - Integer-array item values must also be JSON numbers: `{"value": 100}` not `{"value": "100"}`
447
+ - If the source value contains non-numeric content (e.g., a resource reference), resolve the reference first, then convert the resolved value to a JSON number
448
+ - If a value cannot be parsed as a number after resolution, log a warning and use `0` as a fallback
449
+
450
+ **Plurals**:
451
+ - Android quantities: `zero`, `one`, `two`, `few`, `many`, `other`
452
+ - HarmonyOS quantities: `zero`, `one`, `two`, `few`, `many`, `other` (same set)
453
+
454
+ ### Step 5: Analyze and Resolve Resource Dependencies
455
+
456
+ Android resources often reference other resources using `@type/name` syntax (e.g., `@color/primary`, `@drawable/icon`). Dependency analysis runs regardless of whether resources came from a decoded APK or source `res/` — the difference is that source-based conversion may have more unresolvable references because library resources are absent.
457
+
458
+ Read `references/dependency-analysis-rules.md` for the complete dependency extraction patterns. Also use `references/resource-mapping-rules.md` during this step to infer resource function, screen ownership, source category, and type category for the mapping document.
459
+
460
+ For Lottie references (`lottie_fileName`, `lottie_rawRes`, `lottie_url`, and factory-method calls in Kotlin/Java code), follow the additional extraction and host-screen-binding rules in `references/lottie-conversion-rules.md`. Each detected Lottie asset must be attributed to the Activity/Fragment that plays it and paired with a suggested HarmonyOS page name; these facts are consumed by the report and mapping document in Step 7 and Step 8.
461
+
462
+ Dependency analysis here is `@type/name`-based and therefore covers only resources that exist in `res/`. It does **not** see the code-defined Compose icons handled in Step 4.1.3 — their references live in compiled code, not in XML. Carry the inventory produced by `scripts/code_vector_icons.js` forward into Step 6, Step 7, and Step 8 alongside the `res/`-derived records; do not expect this step to rediscover them.
463
+
464
+ #### 5.1 Build a Resource Value Lookup Table
465
+
466
+ Before resolving references, build a complete lookup table from whichever resource source is being used. This table maps every resource name to its concrete value:
467
+
468
+ - **Value resources** (`colors.xml`, `dimens.xml`, `strings.xml`, etc.): parse all `values*/*.xml` files and build `type → name → value` mappings across all qualifier directories
469
+ - **File resources** (`drawable/`, `mipmap/`): record `type → name → file_path` for each image or XML drawable
470
+ - **Mapping support metadata**: build and maintain side tables for Android screen candidates, resource usage sites, module ownership, and likely library provenance so these can be attached to inventory and mapping rows later
471
+
472
+ When working from source `res/`, this table will be **incomplete** — it won't contain resources defined in library dependencies. That's expected. The dependency analysis in Step 5.3 will identify these gaps.
473
+
474
+ #### 5.2 Resolve References to True Values
475
+
476
+ Scan all converted HarmonyOS resource files and replace resource references with their resolved concrete values:
477
+
478
+ **In element JSON files** (`color.json`, `float.json`, `string.json`, etc.):
479
+ - If a value field contains a HarmonyOS reference like `$color:name`, `$float:name`, `$string:name`, etc., look up the referenced resource in the lookup table
480
+ - Replace the reference with the actual resolved value
481
+ - Follow reference chains (resource A → resource B → concrete value) up to 5 levels deep to handle transitive references
482
+ - If a reference cannot be resolved (e.g., it points to a library resource not in source `res/`, or to a theme attribute `?attr/name`), keep the reference as-is and log it in the report
483
+
484
+ **Android system resource references** (`@android:color/*`, `@android:dimen/*`, `@android:string/*`, `@android:integer/*`):
485
+
486
+ These reference Android framework built-in resources that do NOT exist in HarmonyOS. They MUST be resolved to concrete values — never leave `@android:` references in the output. Use the following built-in lookup table for `@android:color/*`:
487
+
488
+ | Android System Color | Hex Value |
489
+ |---|---|
490
+ | `@android:color/white` | `#ffffffff` |
491
+ | `@android:color/black` | `#ff000000` |
492
+ | `@android:color/transparent` | `#00000000` |
493
+ | `@android:color/background_dark` | `#ff000000` |
494
+ | `@android:color/background_light` | `#ffffffff` |
495
+ | `@android:color/darker_gray` | `#ffaaaaaa` |
496
+ | `@android:color/holo_blue_bright` | `#ff00ddff` |
497
+ | `@android:color/holo_blue_dark` | `#ff0099cc` |
498
+ | `@android:color/holo_blue_light` | `#ff33b5e5` |
499
+ | `@android:color/holo_green_dark` | `#ff669900` |
500
+ | `@android:color/holo_green_light` | `#ff99cc00` |
501
+ | `@android:color/holo_orange_dark` | `#ffff8800` |
502
+ | `@android:color/holo_orange_light` | `#ffffbb33` |
503
+ | `@android:color/holo_purple` | `#ffaa66cc` |
504
+ | `@android:color/holo_red_dark` | `#ffcc0000` |
505
+ | `@android:color/holo_red_light` | `#ffff4444` |
506
+ | `@android:color/primary_text_dark` | `#ffffffff` |
507
+ | `@android:color/primary_text_light` | `#de000000` |
508
+ | `@android:color/secondary_text_dark` | `#b3ffffff` |
509
+ | `@android:color/secondary_text_light` | `#8a000000` |
510
+ | `@android:color/tab_indicator_text` | `#ff808080` |
511
+ | `@android:color/tertiary_text_dark` | `#80ffffff` |
512
+ | `@android:color/tertiary_text_light` | `#54000000` |
513
+ | `@android:color/widget_edittext_dark` | `#ff000000` |
514
+
515
+ For `@android:color/*` values not in this table: if converting from a decoded APK, look up the color in the decoded framework resources (`res/values/colors.xml` from the APK). Otherwise, log a warning and use `#ff000000` (black) as a safe fallback.
516
+
517
+ For `@android:dimen/*` system dimensions: use the following common values:
518
+
519
+ | Android System Dimen | Value |
520
+ |---|---|
521
+ | `@android:dimen/app_icon_size` | `48vp` |
522
+ | `@android:dimen/thumbnail_width` | `96vp` |
523
+ | `@android:dimen/thumbnail_height` | `96vp` |
524
+ | `@android:dimen/dialog_min_width_major` | `65%` |
525
+ | `@android:dimen/dialog_min_width_minor` | `95%` |
526
+ | `@android:dimen/notification_large_icon_width` | `64vp` |
527
+ | `@android:dimen/notification_large_icon_height` | `64vp` |
528
+
529
+ For any other `@android:type/*` not covered by the tables above, log a warning in the report and substitute a reasonable default value for the type (colors → `#ff000000`, dimens → `0vp`, integers → `0`, strings → `""`).
530
+
531
+ **Example — system color resolution:**
532
+ ```json
533
+ // Before resolution:
534
+ {"name": "bg_default", "value": "@android:color/white"}
535
+
536
+ // After resolution:
537
+ {"name": "bg_default", "value": "#ffffffff"}
538
+ ```
539
+
540
+ **Example — color reference resolution:**
541
+ ```json
542
+ // Before resolution:
543
+ {"name": "primary_light", "value": "$color:primary"}
544
+
545
+ // After resolution (primary resolves to #ff6200EE):
546
+ {"name": "primary_light", "value": "#ff6200EE"}
547
+ ```
548
+
549
+ **Example — dimension reference resolution:**
550
+ ```json
551
+ // Before resolution:
552
+ {"name": "margin_double", "value": "$float:margin_base"}
553
+
554
+ // After resolution (margin_base resolves to "16vp"):
555
+ {"name": "margin_double", "value": "16vp"}
556
+ ```
557
+
558
+ **In SVG files** (converted XML drawables):
559
+ - If a fill or stroke color was a resource reference, resolve it to the actual hex color during SVG generation
560
+ - If a dimension was a resource reference, resolve it to the actual numeric value
561
+
562
+ **In layered-image JSON files**:
563
+ - `@drawable/xxx` and `@mipmap/xxx` references → keep as `$media:xxx` (these are file references, not value references — the files themselves are converted)
564
+ - `@color/xxx` references → resolve to hex and generate solid-color PNG (as described in section 4.1.1)
565
+
566
+ **Fallback strategy for unresolvable references in element JSON files:**
567
+
568
+ After completing the reference resolution pass, scan all element JSON files for any remaining unresolved `$type:xxx` references. These MUST be replaced with concrete fallback values — leaving unresolved references in the output will cause HarmonyOS compilation errors.
569
+
570
+ - When the resource source is a **decoded APK** (which contains the complete merged resource set), unresolved references indicate a bug in the resolution logic. Still apply fallbacks, but log prominently as errors.
571
+ - When the resource source is **source `res/`**, unresolved references are expected for library resources. Apply fallbacks and log as warnings.
572
+
573
+ **Fallback values by type:**
574
+
575
+ | Reference Type | Fallback Value | Notes |
576
+ |---|---|---|
577
+ | `$color:xxx` | `#ff000000` (black) | Safe default for any missing color |
578
+ | `$float:xxx` | `0vp` | Zero-dimension fallback |
579
+ | `$string:xxx` | `""` (empty string) | Empty string fallback |
580
+ | `$integer:xxx` | `0` | Zero integer fallback |
581
+ | `$boolean:xxx` | `false` | False boolean fallback |
582
+
583
+ **Resolution for cross-file references:**
584
+ - When building the lookup table (Step 5.1), ensure ALL qualifier directories are scanned, not just `base/`. Color references may chain across qualifier variants (e.g., `base/` defines `primary`, `dark/` overrides it). Use the `base/` variant as the canonical value for resolution.
585
+ - When a reference exists in a non-base qualifier but not in `base/`, include it in the lookup table with its qualifier noted.
586
+
587
+ **Example — fallback applied:**
588
+ ```json
589
+ // Before fallback (unresolvable library color):
590
+ {"name": "design_fab_stroke_top_outer_color", "value": "$color:design_fab_stroke_top_outer_color"}
591
+
592
+ // After fallback:
593
+ {"name": "design_fab_stroke_top_outer_color", "value": "#ff000000"}
594
+ // Report logs: "WARNING: Unresolved $color:design_fab_stroke_top_outer_color — replaced with fallback #ff000000. Likely source: com.google.android.material:material"
595
+ ```
596
+
597
+ #### 5.3 Verify All Dependencies Are Satisfied
598
+
599
+ After resolution, scan all converted resources for any remaining unresolved references:
600
+
601
+ 1. **Converted resource dependencies**: check that every `$media:xxx`, `$color:xxx`, `$float:xxx`, etc. reference in converted files points to an existing resource in the HarmonyOS output
602
+ 2. **Layout and menu file dependencies**: scan all `layout*/` and `menu/` files from the resource source for `@type/name` references. Even though layouts and menus won't be migrated as-is (ArkUI uses declarative UI), the resources they reference — strings, colors, drawables, dimensions — must exist in the converted output so the developer has everything needed when rebuilding the UI
603
+ 3. Mark each dependency as **satisfied** or **unsatisfied**
604
+ 4. **When using source `res/`**: unsatisfied dependencies are expected — they likely come from library resources. Parse `build.gradle` / `build.gradle.kts` to identify declared library dependencies (e.g., AndroidX, Material Components, third-party libraries). In the report, correlate unsatisfied references with likely library sources where possible (e.g., `@color/material_xxx` likely comes from Material Components library)
605
+ 5. Use the dependency graph and reference locations to infer screen ownership for resources whenever possible. If a resource is referenced from multiple screens, record all of them or mark it `Common`; if it is app-wide (launcher icon, theme resource, app name), mark it `Global` or `Launcher` as appropriate.
606
+
607
+ ### Step 6: Verify Completeness
608
+
609
+ Items 1-9 and 11 below are performed by `scripts/a2h_resource_convert.js` and reported in `records.json`; items 10 and 12 are the two separate gates (`svg_fidelity_check.js` and `code_vector_icons.js`). Your job is to confirm each one actually ran and to act on what it found — a gate whose output nobody read is not a gate.
610
+
611
+ The checks are:
612
+
613
+ 1. Build a list of all resource files found in the resource source directory
614
+ 2. For each, check if it was converted or marked as unmappable
615
+ 3. For converted resources, verify the target file exists in the HarmonyOS project
616
+ 4. For `values/*.xml` entries, verify each individual resource entry (string, color, dimen, etc.) appears in the corresponding JSON file
617
+ 5. Flag any resources that were missed
618
+ 6. Verify all dependencies from converted resources are satisfied (all references resolved to true values)
619
+ 7. Verify all dependencies from layout and menu files are satisfied — any missing resources should appear in the Unsatisfied Dependencies section of the report
620
+ 8. **Cross-validate with existing HarmonyOS UI code**: If `harmony_project_dir` already contains `.ets` source files (i.e., it's an existing project with UI code already converted), scan ALL `.ets` files for `$r('app.media.xxx')` resource references and cross-check against the converted `media/` resources:
621
+ a. For each `$r('app.media.xxx')` reference found in `.ets` files, verify that a corresponding file `xxx.*` (any extension: .png, .svg, .jpg, etc.) exists in the output `resources/base/media/` directory
622
+ b. For any referenced but missing media resource, **create a placeholder SVG** file: a simple 24x24 SVG with a colored rectangle and the resource name as text, placed in `base/media/`. This ensures the project compiles.
623
+ c. Log all placeholder-created resources prominently in the conversion report under a dedicated "Placeholder Resources Created" section, so the developer knows these need to be replaced with real assets
624
+ d. Also scan for `$r('app.string.xxx')`, `$r('app.color.xxx')`, `$r('app.float.xxx')` references and verify those exist in the corresponding element JSON files
625
+ e. For any `lottie.loadAnimation({ path: 'lottie/xxx.json' })` (or equivalent `@ohos/lottie` call) found in `.ets` files, verify that `xxx.json` exists at `rawfile/lottie/xxx.json`. If missing, do NOT create a placeholder — Lottie animations have no meaningful placeholder — instead log it as an unresolved Lottie reference in the report so the developer can drop in the correct JSON or fix the path.
626
+ 9. Before finishing verification, ensure every Android resource inventory row and every mapping row has the required metadata fields from `references/resource-mapping-rules.md`. If some fields cannot be proven, populate them with the rule-defined fallback labels (`Unknown`, `Common`, `Global`, `N/A`) rather than leaving them blank.
627
+ 10. **Vector content fidelity (static, required)**: steps 1-9 above all check *existence* — that a file or key is present. **A file that exists and parses can still be visually wrong**, and existence checks cannot detect a dropped `clip-path` or a `fill` injected onto a stroke-only path. Run the bundled checker:
628
+
629
+ ```
630
+ node ./scripts/svg_fidelity_check.js --android-res <resource_source_res_dir> \
631
+ --harmony-media <harmony_project_dir>/<ui_module>/src/main/resources/base/media \
632
+ --json <harmony_project_dir>/svg_fidelity.json
633
+ ```
634
+
635
+ It statically compares each Android `<vector>` against its converted `.svg` and reports `CLIP_PATH_LOST`, `FILL_INJECTED`, `FILL_LOST`, `GRADIENT_LOST`, `VIEWBOX_MISMATCH`, and `STROKE_ONLY_WITHOUT_FILL_NONE` (exit code 1 when any error is found; no device or emulator involved). Write the findings into the report section **Vector Fidelity Issues** with columns file / rule / severity / Android evidence / SVG evidence.
636
+
637
+ **Any `error`-severity finding blocks completion**: re-convert those drawables per the three-branch `fill` rule and the mandatory `clip-path` rule in `references/xml-drawable-to-svg-rules.md`, then re-run until only warnings remain. Do not mark the conversion complete while errors are outstanding, and do not silently re-emit the same output — a defect here propagates into every page that later references the icon, where it will again pass existence-based checks.
638
+
639
+ **Critical: STROKE_ONLY_WITHOUT_FILL_NONE detection** — This error indicates a `<path>` element has a `stroke` attribute but no `fill` attribute. Without an explicit `fill="none"`, SVG's default `fill="black"` applies, turning outline icons into solid blobs. The converter MUST add `fill="none"` to every stroke-only path during generation, not as a post-processing fix. See the mandatory stroke-only detection function in `references/xml-drawable-to-svg-rules.md` for the runtime check that prevents this defect.
640
+ 11. **String escape self-check**: `references/conversion-rules.md` already requires stripping the outer quotes from Android's `"…"` quoting syntax (which exists to protect leading/trailing whitespace, and is *not* part of the literal text). Verify it was actually applied: scan every `element/string.json` for values that both begin and end with a `"` character. Each hit renders visible quote marks in the UI. Fix them and list them in the report. Apply the same check to every qualifier directory (`base/`, locale variants, etc.), not just `base/`.
641
+ 12. **Code-defined vector icon completeness**: steps 1-11 all reason about `res/`. If Step 4.1.3 applied, verify its output too:
642
+ a. Every icon reference in `code_vector_icons.json` is either `resolved` with its `.svg` present in `base/media/`, or carries an explicit `reason` and appears as an `unmappable` row in the report and mapping document. There is no third state — a reference that is neither written nor recorded is a silent omission.
643
+ b. No synthesized icon overwrote a `res/`-derived media file. The helper renames on collision (`…_material.svg`); confirm the rename is recorded in `notes`.
644
+ c. The pinned snapshot ref used for artwork is recorded in the report and in the mapping document metadata. A run whose artwork came from a moving branch is not reproducible and must be re-run against a pinned ref.
645
+
646
+ Unlike item 10, a failure here does not block completion — an unresolved icon is a recorded gap, not a corrupt asset. But an *unrecorded* icon does block: it reproduces exactly the defect this step exists to catch.
647
+
648
+ **Placeholder SVG template** (for missing media resources):
649
+ ```svg
650
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
651
+ <rect width="24" height="24" fill="#CCCCCC"/>
652
+ <text x="12" y="16" font-size="4" text-anchor="middle" fill="#666666">RESOURCE_NAME</text>
653
+ </svg>
654
+ ```
655
+ Replace `RESOURCE_NAME` with the actual resource name (truncated if too long).
656
+
657
+ ### Step 7: Generate Report
658
+
659
+ The report is written by Toolchain step 4 (`--docs-only --report`). It is deliberately **aggregated** so a human can read it end to end: counts by type, qualifier mappings, the full reference-resolution table, unmappable reasons grouped by cause, fidelity findings, code-defined icons, and warnings grouped by kind with a per-kind sample. The exhaustive per-resource listing belongs in the mapping document (Step 8), not here.
660
+
661
+ Your job is to read it and act on what it says — an unresolved reference, a fidelity error, or a target collision is a finding, not a formatting detail.
662
+
663
+ The generated report follows this structure:
664
+
665
+ ```
666
+ # Android to HarmonyOS Resource Conversion Report
667
+
668
+ ## Resource Source
669
+ - APK: <apk_path>
670
+ - Decode result: Success / Failed
671
+ - Decode error (if failed): <error summary>
672
+ - Resource source: <"decoded APK (complete)" / "source res/ (may be missing library resources)">
673
+ - Resource source path: <path>
674
+ - Application module: <app_module>
675
+
676
+ ⚠️ **Note** (only when the decode failed and source `res/` was used): Resources were converted from the project's source `res/` directory because the APK could not be decoded. Library-provided resources (from Maven dependencies, AARs) are NOT included. References to library resources will appear as unsatisfied dependencies below. To get a complete conversion, supply an APK that decodes cleanly and re-run.
677
+
678
+ ## Library Dependencies (from build.gradle)
679
+ Libraries declared in the project's build.gradle that may provide resources:
680
+ | Library | Group:Artifact | Likely Resource Prefixes |
681
+ |---|---|---|
682
+ | Material Components | com.google.android.material:material | @color/material_*, @dimen/material_*, @style/Widget.Material* |
683
+ | AndroidX AppCompat | androidx.appcompat:appcompat | @color/abc_*, @drawable/abc_* |
684
+ | ... | ... | ... |
685
+
686
+ (This section helps identify which unsatisfied dependencies come from which libraries.)
687
+
688
+ ## Summary
689
+ - Resource source: <decoded APK / source res/>
690
+ - Total Android resource files found: <count>
691
+ - Successfully converted: <count>
692
+ - Unmappable (no HarmonyOS equivalent): <count>
693
+ - Failed: <count>
694
+ - Resource references resolved: <count>
695
+ - Unresolved references (library resources missing): <count>
696
+ - Unresolved references (other reasons): <count>
697
+ - Total Lottie animations found: <count>
698
+ - Lottie migrated to rawfile/lottie/: <count>
699
+ - Lottie remote / URL (no local target): <count>
700
+ - Lottie filename collisions disambiguated: <count>
701
+ - Code-defined icon references found (dex): <count | "none detected">
702
+ - Code-defined icons resolved to artwork: <count>
703
+ - Code-defined icons unresolved (recorded as unmappable): <count>
704
+ - Code-defined icon media files written: <count>
705
+ - Code-defined icons screen-attributed / Unknown: <count> / <count>
706
+ - Material Icons snapshot ref: <pinned commit or vendored snapshot path>
707
+
708
+ ## Conversion Details
709
+
710
+ ### Successfully Converted Resources
711
+ | Android Source | HarmonyOS Target | Type | Notes |
712
+ |---|---|---|---|
713
+ | res/values/strings.xml | resources/base/element/string.json | values→element | 25 strings converted |
714
+ | res/drawable/icon.png | resources/base/media/icon.png | media copy | Direct copy |
715
+ | res/drawable-hdpi/bg.png | resources/ldpi/media/bg.png | qualified media | hdpi→ldpi |
716
+ | ... | ... | ... | ... |
717
+
718
+ ### Qualifier Mappings Applied
719
+ | Android Qualifier Dir | HarmonyOS Qualifier Dir | Files Converted |
720
+ |---|---|---|
721
+ | drawable-hdpi | ldpi/media | 5 |
722
+ | values-ar | ar/element | 3 |
723
+ | ... | ... | ... |
724
+
725
+ ### Resource Reference Resolution
726
+ References in converted resources that were resolved to their actual values.
727
+
728
+ | Resource File | Reference | Resolved Value | Resolution Chain |
729
+ |---|---|---|---|
730
+ | base/element/color.json → primary_light | $color:primary | #ff6200EE | @color/primary → @color/md_blue_500 → #6200EE |
731
+ | base/element/float.json → margin_double | $float:margin_base | 16vp | @dimen/margin_base → 16dp → 16vp |
732
+ | ... | ... | ... | ... |
733
+
734
+ ### Layout & Menu Resource Dependencies
735
+ Dependencies from layout/menu files (not converted to HarmonyOS, but needed when rebuilding the UI in ArkUI).
736
+
737
+ | Source File (not converted) | Dependencies | All Satisfied? |
738
+ |---|---|---|
739
+ | layout/activity_main.xml | @string/app_name ✅, @drawable/bg_header ✅, @color/primary ✅ | Yes |
740
+ | layout/fragment_settings.xml | @string/settings_title ✅, @drawable/ic_back ❌ (library) | No |
741
+ | menu/main_menu.xml | @string/menu_share ✅, @drawable/ic_share ❌ (library) | No |
742
+ | ... | ... | ... |
743
+
744
+ ### Unmappable Resources
745
+ | Android Source | Reason |
746
+ |---|---|
747
+ | res/layout/activity_main.xml | Layout XML has no HarmonyOS equivalent (use ArkUI) |
748
+ | res/drawable/ripple_effect.xml | XML drawable not supported |
749
+ | ... | ... |
750
+
751
+ ### Unmapped Resources (Unsupported Qualifiers)
752
+ | Android Source Dir | Qualifier | Reason |
753
+ |---|---|---|
754
+ | res/values-sw600dp/ | sw600dp | No HarmonyOS equivalent — skipped |
755
+ | res/values-w480dp/ | w480dp | No HarmonyOS equivalent — skipped |
756
+ | ... | ... | ... |
757
+
758
+ ### Unresolved References (Fallback Applied)
759
+ | Resource File | Original Reference | Fallback Value | Reason | Likely Source |
760
+ |---|---|---|---|---|
761
+ | drawable/bg_themed.xml | ?attr/colorSurface | N/A (kept as-is) | Theme attribute — cannot resolve statically | N/A |
762
+ | element/color.json → material_blue | @color/design_default_color_primary | #ff000000 | Library resource not in source res/ | com.google.android.material:material |
763
+ | ... | ... | ... | ... | ... |
764
+
765
+ ### System Resources Resolved
766
+ | Resource File | System Reference | Resolved Value |
767
+ |---|---|---|
768
+ | element/color.json → bg_white | @android:color/white | #ffffffff |
769
+ | element/color.json → divider | @android:color/darker_gray | #ffaaaaaa |
770
+ | ... | ... | ... |
771
+
772
+ ### Nine-Patch Files Renamed
773
+ | Original Name | Renamed To | Source Directory |
774
+ |---|---|---|
775
+ | abc_btn_default.9.png | abc_btn_default_9.png | drawable-mdpi |
776
+ | ... | ... | ... |
777
+
778
+ ### Lottie Animation Resources
779
+ Every Lottie JSON detected in the project (identified by structure signature: `v`+`fr`+`ip`+`op`+`layers`) and where it lands in HarmonyOS. Each row also identifies the Android Activity/Fragment that plays the animation and proposes the corresponding HarmonyOS page name so the developer knows where to call `@ohos/lottie`'s `lottie.loadAnimation({ path: 'lottie/<name>.json' })`.
780
+
781
+ | Android Source | Target Rawfile Path | Host Activity/Fragment | Host Layout / Loader | HarmonyOS Page (suggested) | Notes |
782
+ |---|---|---|---|---|---|
783
+ | assets/AndroidWave.json | rawfile/lottie/androidwave.json | DynamicActivity (via lottie_fileName) | res/layout/dynamic_activity.xml | DynamicPage | XML `lottie_fileName` reference |
784
+ | assets/Lottie Logo 1.json | rawfile/lottie/lottie_logo_1.json | PlayerActivity → PlayerFragment | PreviewFragment picker (AssetManager.list) → PlayerActivity.intent(...) | PlayerPage | Renamed from "Lottie Logo 1.json" (spaces normalized). Loaded via runtime picker, dispatches to PlayerActivity. |
785
+ | res/raw/lottielogo.json | rawfile/lottie/lottielogo.json | (multiple) | res/layout/auto_play.xml, auto_play_gone.xml, no_auto_play.xml (`lottie_rawRes="@raw/lottielogo"`) | AutoPlayPage / NoAutoPlayPage | Referenced by three demo layouts. |
786
+ | (remote) https://... | N/A | ... | code call to LottieCompositionFactory.fromUrl(...) | ... | Remote resource, no local target. |
787
+ | ... | ... | ... | ... | ... | ... |
788
+
789
+ ### Code-Defined Vector Icons
790
+ Icons the Android app draws from code (`androidx.compose.material:material-icons-*`) rather than from `res/`. These never appear in the decoded resource tree, so they are recovered from `classes*.dex` and synthesized from the pinned Material Icons snapshot. Omit this section only when no code-defined icons were detected.
791
+
792
+ Artwork source: `<pinned commit or vendored snapshot path>`
793
+
794
+ | Compose Reference | Material Name | Style | HarmonyOS Target | Host Screen(s) | Suggested Page | Notes |
795
+ |---|---|---|---|---|---|---|
796
+ | Icons.Outlined.GetApp | get_app | Outlined | base/media/ic_get_app_outlined.svg | MoreScreen | MorePage | |
797
+ | Icons.AutoMirrored.Outlined.Label | label | Outlined | base/media/ic_label_outlined.svg | MoreScreen | MorePage | AutoMirrored: mirror horizontally under right-to-left locales |
798
+ | Icons.Filled.DateRange | date_range | Filled | base/media/ic_date_range_filled.svg | Unknown | — | dex-only; no source reference found (likely used by a library) |
799
+ | ... | ... | ... | ... | ... | ... | ... |
800
+
801
+ Unresolved references (no artwork produced — these are real gaps, list every one):
802
+
803
+ | Compose Reference | Material Name | Reason |
804
+ |---|---|---|
805
+ | ... | ... | not found in Material Icons index / app-defined ImageVector / snapshot unavailable |
806
+
807
+ ### Placeholder Resources Created
808
+ Media resources referenced in `.ets` UI code but not found in converted resources. Placeholder SVGs were created to allow compilation — these MUST be replaced with real assets.
809
+ | Resource Name | Referenced In | Placeholder File |
810
+ |---|---|---|
811
+ | ic_tab_home | pages/MainPage.ets | base/media/ic_tab_home.svg |
812
+ | ic_tab_profile | pages/MainPage.ets | base/media/ic_tab_profile.svg |
813
+ | ... | ... | ... |
814
+
815
+ ### Failed Conversions
816
+ | Android Source | Error |
817
+ |---|---|
818
+ | ... | ... |
819
+
820
+ ### Verification Results
821
+ - All resources accounted for: Yes/No
822
+ - Missing resources: <list if any>
823
+ - All references resolved: Yes/No
824
+ - Unresolved references: <count> (<count> due to missing library resources, <count> other)
825
+ - Vector fidelity errors: <count> (warnings: <count>) — from `scripts/svg_fidelity_check.js`
826
+ - String escape artifacts: <count> (values still wrapped in literal quotes)
827
+ - Code-defined icons unrecorded: <count> (MUST be 0 — every reference is either written or listed as unmappable)
828
+
829
+ ### Vector Fidelity Issues
830
+ <Omit this section only when the checker reports zero findings.>
831
+
832
+ | File | Rule | Severity | Android evidence | SVG evidence |
833
+ |---|---|---|---|---|
834
+ | <name> | CLIP_PATH_LOST / FILL_INJECTED / FILL_LOST / GRADIENT_LOST / VIEWBOX_MISMATCH | error/warn | <clip count, fillColor, gradient stops, viewport…> | <clipPath count, fill value, gradient defs, viewBox…> |
835
+ ```
836
+
837
+ ### Step 8: Generate Resource Mapping Document
838
+
839
+ The second document, at `resource_mapping_path`, is written by the same Toolchain step 4 invocation (`--mapping`). It is the audit surface: **every** observed Android resource appears as its own row, `values` at entry granularity. It does not sample, summarise, or aggregate — those are the report's job. Expect it to be large (measured range: 4 MB to 22 MB); that size is the cost of the no-omission requirement, and the dataset tracks these files with Git LFS for exactly that reason.
840
+
841
+ Do not hand-write or hand-trim this document. If it looks too large, that is not a reason to sample it.
842
+
843
+ The generated mapping markdown contains these required sections:
844
+
845
+ 1. **Metadata**
846
+ - `android_project_dir`
847
+ - `harmony_project_dir`
848
+ - Resource source
849
+ - Resource source path
850
+ - Decode result
851
+ - Generation timestamp
852
+ - Material Icons snapshot ref — only when Step 4.1.3 applied; the pinned commit or vendored snapshot path used to synthesize code-defined icons
853
+
854
+ 2. **Android Resource Inventory**
855
+ - One row per Android resource item
856
+ - Required columns: Android Resource Path, Resource Name, Function, Screen(s), Source Category, Type Category, Status, Notes
857
+
858
+ 3. **Android → HarmonyOS Mapping Details**
859
+ - One row per mapping record
860
+ - Required columns: Android Resource Path, Android Screen(s), Android Source Category, Android Type Category, HarmonyOS Target, Mapping Kind, Notes
861
+
862
+ 4. **Unmapped / Unmappable / System / Remote Summary**
863
+ - Explicitly list resources with no direct HarmonyOS target, Android framework resources, library-only resources inferred from the APK or dependency graph, and runtime remote resources
864
+
865
+ 5. **Code-Defined Vector Icons** (only when Step 4.1.3 applied)
866
+ - One row per icon reference recovered from `classes*.dex`, including references that could not be resolved
867
+ - Required columns: Compose Reference, Material Name, Style, HarmonyOS Target (or `N/A`), Host Screen(s), Suggested HarmonyOS Page, Notes
868
+ - These rows are additional to — not a substitute for — the inventory and mapping rows required in sections 2 and 3. Record them there with `source_category = 第三方库资源`, `type_category = drawable`, and `mapping_kind = code-vector synthesized` (or `unmappable`), using `<source file>#Icons.<Style>.<Name>` as the Android resource path (or `classes.dex#Icons.<Style>.<Name>` when dex-only)
869
+
870
+ 6. **Quick Findings**
871
+ - Summarize important review findings such as launcher icons from `mipmap`, probable third-party library assets (`abc_*`, `mtrl_*`, `design_*`), and resources present only in the decoded APK but not in source `res/`
872
+
873
+ **Hard requirements for the mapping markdown:**
874
+ - Never omit an Android resource silently
875
+ - Never omit a code-defined icon reference silently — an icon that could not be resolved is an `N/A` row with a stated reason, never an absent row
876
+ - For `values/*.xml`, inventory and mapping rows must be at entry granularity using `file_path#tag/name`
877
+ - Every mapping row must include the Android-side Screen(s), Source Category, and Type Category columns
878
+ - If one Android resource maps to multiple HarmonyOS outputs, emit multiple mapping rows
879
+ - If no HarmonyOS target exists, write `N/A` in the HarmonyOS Target column and explain why in Notes
880
+
881
+
882
+ ## Important Notes
883
+
884
+ - **Never write your own conversion script.** `scripts/a2h_resource_convert.js` is the canonical implementation of every rule in `references/`; `scripts/code_vector_icons.js` and `scripts/svg_fidelity_check.js` complete the toolchain. Re-implementing any of it produces results that are not comparable across runs — that is the measured failure this toolchain exists to remove (see "Toolchain — mandatory"). If a rule appears wrong or a case is unhandled, fix the script and say so; do not route around it with a one-off converter, a hand-edited resource, or an undocumented placeholder.
885
+ - **`scripts/code_vector_icons.js` is required, not optional, for Jetpack Compose applications.** Their icons live in `classes*.dex`, not `res/`, so every `res/`-based check in this skill will report success while the migrated page renders no icons at all. Skipping it is the one omission that passes every other gate.
886
+ - Always read `references/conversion-rules.md` for the complete, detailed conversion rules before starting conversions. The tables above are summaries.
887
+ - Always read `references/resource-mapping-rules.md` before producing `resource_mapping_path`. It defines the inventory schema, mapping schema, screen attribution rules, source categorization rules, type categorization rules, and markdown output format.
888
+ - **Decoding is best-effort**: `apk_path` is decoded first because the decoded APK is the most complete resource source (it includes library dependencies merged in at build time). This skill does **not** build the APK — it decodes the one the caller supplies. If decoding fails, conversion proceeds from the project's source `res/` at `<android_project_dir>/<app_module>/src/main/res/` — a partial conversion with clear reporting is far more useful than no conversion at all.
889
+ - **XML drawable conversion is critical**: Android XML drawables (`<vector>`, `<shape>`, `<layer-list>`, `<selector>`) must be converted to SVG format, not skipped. HarmonyOS supports SVG in `media/`. Read `references/xml-drawable-to-svg-rules.md` for the detailed conversion rules for each drawable type.
890
+ - **Lottie animations are special**: Android projects that use `com.airbnb.android:lottie` ship JSON files in `assets/` or `res/raw/`. These migrate to `<ui_module>/src/main/resources/rawfile/lottie/<normalized>.json` for consumption by `@ohos/lottie` (installed via `ohpm install @ohos/lottie`). Identify Lottie JSON by structure signature (must contain `v`+`fr`+`ip`+`op`+`layers`), not by extension. Every Lottie row in the mapping document MUST bind the JSON to the Android Activity/Fragment that plays it and propose a HarmonyOS page name. Read `references/lottie-conversion-rules.md` for the complete rules including reference extraction (`lottie_fileName` / `lottie_rawRes` / `lottie_url`), filename normalization, host-screen binding, and remote/dynamic edge cases.
891
+ - **Code-defined icons are invisible to `res/` scanning**: Jetpack Compose apps draw most icons from `androidx.compose.material:material-icons-*`. Those are compiled `ImageVector` objects in `classes*.dex` — not in `res/drawable*/`, not in `resources.arsc`, therefore not in the decoded `res/` tree. Every existence-based check in this skill will report success while the migrated page shows no icons. Step 4.1.3 and `scripts/code_vector_icons.js` recover them: the dex gives the authoritative inventory, the Android source gives screen attribution, and a **pinned** Material Icons snapshot gives the artwork. Read `references/code-vector-icon-rules.md` for the complete rules. An icon this process cannot resolve must still become a visible `unmappable` row — never a silent omission.
892
+ - **Dependency analysis always runs**: Read `references/dependency-analysis-rules.md` for the complete dependency extraction patterns and reference formats. Even when converting from source `res/` with missing library resources, dependency analysis is essential — it tells the developer exactly which references are broken and why, so they can address them manually or fix the build.
893
+ - **APK decoding**: The decoder is `a2h-resource`, a pure-Node.js resource decoder published to npm (requires Node.js `>= 20`; no other runtime). Install it with `npm install -g a2h-resource` and run `a2h-resource <apk> -o <out>`, or run it on-demand with `npx --yes a2h-resource <apk> -o <out>`. It covers resource extraction only — it does not disassemble `classes.dex` or repackage APKs, neither of which this skill needs. It decodes a single resource package (the app's); if it errors out or the APK is a rare multi-package/split APK, fall back to converting from the project's source `res/` directory (as in Step 3).
894
+ The following are **behaviours the converter already guarantees**. They are listed so you know what to expect and what to check in its output — not as instructions to implement:
895
+
896
+ - Android XML is parsed with namespaces, comments, and CDATA handled; attributes are matched by local name, so any `android:` prefix works.
897
+ - Every entry of every `<resources>` element is extracted; multiple value files contributing the same type are merged into one HarmonyOS JSON file.
898
+ - Resource names are preserved exactly as Android spells them — they are identifiers.
899
+ - An element JSON that already exists in the HarmonyOS project is **merged**, not overwritten: existing entries keep their order and position, DevEco scaffold defaults are replaced when a real resource has the same name, and the replacement is recorded.
900
+ - Android's `"…"` quoting syntax is consumed as syntax, so leading and trailing whitespace it protects is preserved in the value. Do not "clean up" such strings; Android renders that whitespace.
901
+ - Decoded fixed-point dimensions are rounded to 3 decimals, so values like `6.500001dip` do not carry float noise into the output.
902
+ - When several Android sources map to one HarmonyOS target (`drawable/x.xml` and `drawable-anydpi/x.xml` both land in `base/media/`), the source with **fewer qualifiers** wins and the loser is recorded as an `unmappable` mapping row — never silently dropped.