# Android to HarmonyOS Lottie Animation Conversion Rules

This document defines how to migrate Lottie animation resources from an Android project to a HarmonyOS project that uses `@ohos/lottie`. Read this before converting any Lottie animation. Every Lottie resource observed during conversion must produce an inventory row and a mapping row per the rules in `references/resource-mapping-rules.md`.

## Table of Contents
1. [Scope and Runtime Context](#scope-and-runtime-context)
2. [How to Identify a Lottie JSON File](#how-to-identify-a-lottie-json-file)
3. [Source Locations in an Android Project](#source-locations-in-an-android-project)
4. [Target Path in the HarmonyOS Project](#target-path-in-the-harmonyos-project)
5. [Filename Normalization](#filename-normalization)
6. [Extracting Lottie References from Android Code](#extracting-lottie-references-from-android-code)
7. [Binding Each Lottie JSON to a HarmonyOS Page](#binding-each-lottie-json-to-a-harmonyos-page)
8. [Unmappable / Deferred Cases](#unmappable--deferred-cases)
9. [Report and Mapping Requirements](#report-and-mapping-requirements)

---

## Scope and Runtime Context

On HarmonyOS the animation library is `@ohos/lottie` (installed via `ohpm install @ohos/lottie`). The JSON files are consumed at runtime via `lottie.loadAnimation({ path: 'lottie/animation.json', ... })`, where `path` is resolved under `entry/src/main/resources/rawfile/`. This skill handles **only** the JSON asset migration and its reference bookkeeping — it does NOT generate ArkTS UI code. The developer rewrites the hosting screen(s) in ArkUI and calls `@ohos/lottie` from that screen using the paths this skill produced.

## How to Identify a Lottie JSON File

The `.json` extension is not enough on its own — HarmonyOS projects can contain unrelated JSON. A file is a Lottie animation JSON if and only if its parsed root object contains **all** of the following top-level fields:

| Field | Meaning |
|---|---|
| `v` | Bodymovin exporter version, e.g. `"5.7.4"` |
| `fr` | Frame rate, number |
| `ip` | In-point (start frame), number |
| `op` | Out-point (end frame), number |
| `w` | Composition width, number |
| `h` | Composition height, number |
| `layers` | Array of layer objects |

If a JSON file has this signature, treat it as a Lottie animation regardless of the directory it lives in. If a JSON file is missing any of these fields, do not classify it as a Lottie asset; process it under the ordinary rules for its host directory (e.g., a raw JSON config in `res/raw/` follows the raw-file rules).

If a file cannot be parsed as JSON (broken, binary), record it as an inventory row with `Status = failed to parse` and `mapping_kind = failed conversion`.

## Source Locations in an Android Project

Android projects place Lottie JSON in one of two directories. This skill scans both:

| Android Location | Access Pattern in Android | Example |
|---|---|---|
| `app/src/main/assets/` (recursive) | `AssetManager.open(...)`, `LottieCompositionFactory.fromAsset(context, "AndroidWave.json")` | `assets/AndroidWave.json`, `assets/lottie/checkmark.json` |
| `app/src/main/res/raw/` | `R.raw.name`, `LottieCompositionFactory.fromRawRes(context, R.raw.lottielogo)` | `res/raw/lottielogo.json` |

`assets/` is a passthrough directory in APK decoding (unlike `res/`), so its files stay under `<decoded_output_path>/assets/` after decoding. Scan both `<decoded_output_path>/assets/` and `<decoded_output_path>/res/raw/` (or the source-fallback equivalents `app/src/main/assets/` and `app/src/main/res/raw/`).

When multi-module: scan every module's `src/*/assets/` and `src/*/res/raw/`, including flavor source sets (`src/debug/`, `src/<flavor>/`).

## Target Path in the HarmonyOS Project

All migrated Lottie animations land under a dedicated subdirectory of `rawfile/`:

```
entry/src/main/resources/rawfile/lottie/<name>.json
```

- The `lottie/` subdirectory keeps them separate from unrelated raw assets and matches the load-path convention used by `@ohos/lottie` examples (`path: 'common/lottie/animation.json'` style paths).
- Preserve the base filename (after normalization — see below). Discard any Android source subdirectory. If a project has `assets/animations/wave.json`, the target is still `rawfile/lottie/wave.json`.
- The load path recorded in the mapping document (for the developer to feed into `lottie.loadAnimation({ path: ... })`) is `lottie/<name>.json` — relative to `rawfile/`, no leading slash.

**Filename collision handling.** If two source files would normalize to the same target filename (e.g., `assets/logo.json` and `res/raw/logo.json` both target `lottie/logo.json`), disambiguate by prefixing the source category: `lottie/assets_logo.json` and `lottie/raw_logo.json`. Record both original paths in the mapping row's `notes`.

## Filename Normalization

HarmonyOS resource filenames must be all-lowercase, use only `[a-z0-9_]` in the base name, and only one `.` before the extension. Apply the following transforms to every Lottie source filename:

1. Strip the `.json` extension, transform the base name, then reattach `.json`.
2. Replace whitespace and any character in `[^a-zA-Z0-9_]` with `_`.
3. Collapse consecutive `_` into a single `_`.
4. Trim leading/trailing `_`.
5. Lowercase the entire base name.
6. If the base name starts with a digit after normalization, prefix with `lottie_`.

Examples:

| Android source filename | Normalized target under `rawfile/lottie/` |
|---|---|
| `AndroidWave.json` | `androidwave.json` |
| `HamburgerArrow.json` | `hamburgerarrow.json` |
| `Lottie Logo 1.json` | `lottie_logo_1.json` |
| `Lottie Logo 2.json` | `lottie_logo_2.json` |
| `lottielogo.json` (from `res/raw/`) | `lottielogo.json` |
| `01-intro.json` | `lottie_01_intro.json` |

When a filename is renamed, record the rename in the mapping row's `notes` (`renamed from "Lottie Logo 1.json"`) and — if the reference is code-loaded via a literal string in Kotlin/Java — flag it in the report so the developer updates the code.

## Extracting Lottie References from Android Code

Lottie assets are wired to Android screens through three attribute-based reference patterns (in XML) plus a code-loaded pattern (in Kotlin/Java). Extend the reference-scanning defined in `references/dependency-analysis-rules.md` with the patterns below.

### XML attribute references

Namespace prefix varies (`app:`, `airbnb:`, custom bindings) — match by local name.

| Attribute | Reference target | Notes |
|---|---|---|
| `lottie_fileName` | Filename under `assets/` (e.g., `"AndroidWave.json"`) | Treat the string value as an assets-relative path. Match to the migrated `rawfile/lottie/<normalized>.json`. |
| `lottie_rawRes` | `@raw/<name>` | Match `<name>.json` inside `res/raw/`. The migrated target is `rawfile/lottie/<normalized>.json`. |
| `lottie_url` | Runtime URL string | No local file to migrate. Mark the reference as **remote** — `mapping_kind = remote resource (no local target)` and `source_category = 运行时远程资源`. Record the URL in `notes`. |
| `lottie_fallbackRes` | `@drawable/<name>` | Not a Lottie asset — falls under the normal drawable rules. Note the association in the Lottie row's `notes` because it is the fallback shown when the animation fails. |

For each XML reference, record the host layout file. That is the input to page binding (next section).

### Code-loaded Lottie

Some apps load Lottie JSON purely from code, including scanning `assets/` at runtime with `AssetManager.list("")`. Detection heuristics:

1. String literal ending in `.json` passed to `LottieCompositionFactory.fromAsset(...)`, `.fromRawRes(...)`, `.fromJsonInputStream(...)`, `.fromUrl(...)`, or to `LottieAnimationView.setAnimation(...)`.
2. `AssetManager.list(...)` results filtered by `.json` and passed into any of the factory methods above — this is the "dynamic dialog listing all assets" pattern (see `PreviewFragment` in the airbnb `lottie-android` sample).
3. Any `CompositionArgs`-like data class carrying an `assetName` / `fileUri` / `url` field consumed by a factory method.

For every code-loaded reference:
- If the source file is `assets/*.json` or `res/raw/*.json` → treat exactly like an XML reference in terms of migration (copy to `rawfile/lottie/<normalized>.json`).
- If the source is a URL or a user-picked file URI → mark as runtime remote resource, no local target.
- Record the Kotlin/Java source file and, if identifiable, the containing Activity/Fragment for page binding.

### Extending the reference table

Add the Lottie patterns above to whatever reference-extraction pipeline the skill runs. They are not covered by the generic `@type/name` regex in `references/dependency-analysis-rules.md`, so they need their own passes:

1. XML pass: for every layout / view XML file, extract every attribute whose local name matches `^lottie_(fileName|rawRes|url|fallbackRes)$`. Value handling is as described above.
2. Code pass: grep the project's Kotlin/Java sources for the factory method names listed above and, for each hit, capture the argument (literal filename, resource id, or URL) plus the enclosing class name.

## Binding Each Lottie JSON to a HarmonyOS Page

The whole point of the mapping report is telling the HarmonyOS developer **which screen in the new project plays which animation**. For every Lottie JSON, produce a *host* attribution using the same evidence sources as the standard screen-attribution rules (`references/resource-mapping-rules.md` §6, `references/dependency-analysis-rules.md` "Screen Attribution"), plus these Lottie-specific rules:

### Attribution priority

1. **XML host layout → hosting Activity/Fragment.**
   - Find the layout XML(s) that reference the JSON via `lottie_fileName` / `lottie_rawRes`.
   - Walk backward: which Activity/Fragment inflates that layout? Evidence includes `setContentView(R.layout.<name>)`, `AppCompatActivity(R.layout.<name>)` constructor form, `BaseFragment(R.layout.<name>)` / `Fragment.onCreateView` returning `R.layout.<name>`, and DataBinding/ViewBinding class names derived from the layout.
   - Record `<Activity/Fragment class name>` in the `Host Activity/Fragment` column and `<layout file>` in `notes`.

2. **Code-loaded (literal filename in factory call).**
   - Take the enclosing class as the host. If the class is a ViewModel or repository (not a screen), walk one hop up to the Fragment/Activity that observes it.

3. **Code-loaded (dynamic list from `AssetManager`).**
   - This is the "preview / picker" pattern where the animation is rendered on whatever detail screen the user navigates to. Attribute every listed JSON to that **detail screen** (the target of the `startActivity` call from the picker), not the picker itself. The picker is the browser; the detail screen is the actual host.
   - Example (from the airbnb `lottie-android` sample): `PreviewFragment` lists `assets/*.json` in an `AlertDialog` and calls `PlayerActivity.Companion.intent(context, CompositionArgs(asset = <name>))`. Attribute all listed JSONs to `PlayerActivity` (which in turn hosts `PlayerFragment` with the `LottieAnimationView`).

4. **Manifest / global.**
   - If the JSON is loaded from `Application.onCreate`, a splash screen, or shared UI (nav header, tab bar), attribute to `Global` or `Launcher` per the shared-component / global rules in `resource-mapping-rules.md`.

5. **Fallback.**
   - If no evidence exists, use `Unknown` and mention the reason in `notes` (e.g., "loaded via runtime string from unrecognized code path").

### Suggested HarmonyOS Page

Alongside the Android host, propose a HarmonyOS page name so the developer knows where to place the `@ohos/lottie` call. Use these rules:

- Strip the `Activity` / `Fragment` suffix and append `Page`. Examples: `PlayerActivity` → `PlayerPage`, `PlayerFragment` → `PlayerPage`, `LoginActivity` → `LoginPage`.
- Multiple hosts collapse to one page if they share the same base name (Activity + Fragment pair for the same screen).
- `Launcher` → `EntryAbility` (or the equivalent entry ability of the HarmonyOS project).
- `Global` → `App` / global scope.
- `Unknown` → `Unknown`.

This is a **suggestion column** — the mapping row's `notes` should say `HarmonyOS page name inferred from Android host; verify against actual ArkUI pages`.

## Unmappable / Deferred Cases

Record each of these with an explicit `mapping_kind` and keep the inventory row, never silently drop:

| Case | `mapping_kind` | Notes |
|---|---|---|
| `lottie_url="https://..."` | `remote resource (no local target)` | HarmonyOS Target = `N/A`. Developer must fetch and re-render at runtime. |
| JSON referenced only by a runtime `Uri` from a file picker (`Intent.ACTION_GET_CONTENT`) | `remote resource (no local target)` | HarmonyOS Target = `N/A`. Developer handles user-provided files with the equivalent Picker API. |
| JSON present in `assets/` but not referenced anywhere in XML or code (dead asset) | `direct copy` still applies | Migrate anyway, mark screens = `Unknown`, note "no static references found — possibly loaded via string concatenation or dead asset". |
| JSON exists but fails Lottie signature check (missing `v`/`fr`/`layers` etc.) | `unmappable` | HarmonyOS Target = `N/A`. Notes: "JSON in assets/raw is not a valid Lottie composition; falls back to raw-file rules". |
| Lottie with embedded image assets (`images` field pointing to external files) | `direct copy` for the JSON + `direct copy` for each referenced image | Copy images alongside the JSON under `rawfile/lottie/images/<name>` (preserve relative structure declared by the Lottie `imagePath`). Record each image as its own inventory + mapping row. |
| Lottie referenced by `lottie_fallbackRes` only (used as fallback for another animation) | `direct copy` | Attribute to same host as the primary animation. Mention "fallback for @drawable/xxx or another JSON" in notes. |

## Report and Mapping Requirements

### Standard conversion report (Step 7)

Add a dedicated section:

```
### Lottie Animation Resources
Every Lottie JSON detected in the project (via structure signature) and where it maps in HarmonyOS.

| Android Source | Target Rawfile Path | Host Activity/Fragment | Host Layout | HarmonyOS Page (suggested) | Notes |
|---|---|---|---|---|---|
| assets/AndroidWave.json | rawfile/lottie/androidwave.json | (none — XML host only) | res/layout/dynamic_activity.xml | DynamicPage | XML host inflated by DynamicActivity (inferred) |
| assets/Lottie Logo 1.json | rawfile/lottie/lottie_logo_1.json | PlayerActivity (via PreviewFragment picker) | N/A | PlayerPage | Renamed from "Lottie Logo 1.json" (spaces stripped). Loaded via AssetManager.list() in PreviewFragment; picker dispatches to PlayerActivity. |
| ... | ... | ... | ... | ... | ... |
```

Also add these summary counters to the "Summary" section:
- `Total Lottie animations found: <count>`
- `Migrated to rawfile/lottie/: <count>`
- `Remote / URL Lottie (no local target): <count>`
- `Filename collisions disambiguated: <count>`

### Mapping markdown (Step 8)

- Every Lottie JSON is one inventory row and at least one mapping row.
- Inventory row: `type_category = raw` (Lottie JSON lives under Android `assets/` or `res/raw/`, both of which normalize to Harmony's `rawfile/`). Explain the Lottie subtype in `notes` (`Lottie animation JSON`).
- Mapping row `harmony_target` values:
  - Local migration → `entry/src/main/resources/rawfile/lottie/<normalized>.json`
  - Remote / URL / user-picked → `N/A` with `mapping_kind = remote resource (no local target)`
- Mapping row `mapping_kind`:
  - `direct copy` — for a straight assets/raw → rawfile move
  - `rename` — used **in addition to** `direct copy` when the filename was normalized (emit as a second mapping row or record both in `notes`; the simpler option is one row with `mapping_kind = direct copy` and a `notes` entry `renamed from "<original>"`)
  - `remote resource (no local target)` — URL / picker cases
  - `unmappable` — JSON that fails the Lottie signature check
- Every Lottie mapping row MUST populate the Host Activity/Fragment (via `notes` if the mapping template does not have a dedicated column) and the suggested HarmonyOS page so the developer can wire the `@ohos/lottie` call to the correct ArkUI page.
- If the mapping markdown template supports extra columns, add `Host Activity/Fragment` and `Suggested HarmonyOS Page` columns to the Lottie section. Otherwise, put both facts in `notes` in the format `host=<class>; page=<PageName>`.

### Cross-check during Step 6 verification

- Verify every migrated Lottie file is present under `rawfile/lottie/` in the HarmonyOS output.
- Verify every `lottie_fileName` / `lottie_rawRes` reference discovered in XML has a corresponding entry in the report's Lottie table.
- If `harmony_project_dir` already contains ArkTS code and any `.ets` file already calls `lottie.loadAnimation({ path: 'lottie/xxx.json' })`, verify the file exists at `rawfile/lottie/xxx.json`. If missing, do **not** create a placeholder SVG (that's the drawable rule) — instead log it prominently as an unresolved Lottie reference so the developer can either drop in the JSON manually or fix the path.
