---
name: android-apk-analysis
title: Android APK Analysis
description: Decompile, explore, and analyze Android APKs to extract architecture patterns, UI/UX patterns, and technical reference data for your own app development. Covers jadx decompilation, systematic source exploration, manifest analysis, and producing reference documentation.
metadata:
  hermes:
    requires_toolsets: [terminal, file]
---

## When to Load

Use this skill whenever you need to:
- Decompile and analyze an Android APK (yours or a competitor/reference app)
- Understand another app's architecture, screen flow, or tech stack
- Extract UI/UX patterns, player implementations, or caching strategies from an APK
- Produce structured audit documents for informing your own app design
- Reverse-engineer API endpoints, provider/resolver patterns, or debrid service integrations
- Copy specific features 1:1 from a reference app into your own app
- Trace source-scraping, debrid-resolution, or addon-discovery pipelines in obfuscated streaming apps

## Workflow

### 0. OPEN-SOURCE SHORTCUT — Always Check First

**Before reaching for jadx, check if the reference app is open source.** If the app has a public GitHub repository (check the app's website, GitHub Releases, or just search `app-name GitHub`), clone the source directly instead of decompiling. Source code is infinitely better than decompiled output — correct variable names, comments, structure, and buildable.

```bash
git clone --depth 1 https://github.com/<owner>/<repo> /tmp/app-source
# If a release tag exists:
git clone --depth 1 --branch <tag> https://github.com/<owner>/<repo> /tmp/app-source
```

**When source is available, skip jadx entirely.** Read the build.gradle.kts for dependencies, AndroidManifest.xml for screens, and navigate the Kotlin source directly. The analysis is faster and the results are much more accurate.

**When the user provides both a GitHub URL and an APK download**, check the GitHub first. If it's the full source, use it. Only decompile if the GitHub is documentation-only, missing the app module, or the source is unavailable.

**When producing implementation documents for your own app**, create a single consolidated plan (e.g., `docs/REFERENCE_TO_TARGET_IMPLEMENTATION.md`) covering: architecture map, files to copy per system, dependency changes needed, integration points, and implementation priority order — rather than the 6-file APK audit deliverable set, which is designed for competitor analysis where you can't copy code directly.

```bash
mkdir -p ~/apk-analysis
cd ~/apk-analysis
curl -sL "$apk_url" -o app-name.apk
```

### 2. Install & Run jadx (if not present)

```bash
cd /tmp
curl -sL "https://github.com/skylot/jadx/releases/download/v1.5.1/jadx-1.5.1.zip" -o jadx.zip
unzip -q jadx.zip -d jadx-1.5.1
```

### 3. Decompile

```bash
cd ~/apk-analysis
mkdir -p app-name-jadx
/tmp/jadx-1.5.1/bin/jadx --show-bad-code -d app-name-jadx app-name.apk
```

Note: `--show-bad-code` includes decompilations that may have errors. Expect some errors (typically 20-60 for obfuscated apps) — these are normal and the bulk of the code is usable.

### 4. Read the Manifest First

```bash
cat app-name-jadx/resources/AndroidManifest.xml
```

Extract immediately:
- Package name, versionCode, versionName
- minSdkVersion, targetSdkVersion, compileSdkVersion
- All permissions
- All activity declarations (these ARE your screen map)
- Application class
- Any LAUNCHER / LEANBACK_LAUNCHER intent filters
- Theme/icon/banner references
- Network security config
- Backup/cleartext settings

### 5. Map Package Structure

```bash
# Find first-party packages (not androidx, com/google, okhttp3 etc.)
find app-name-jadx/sources/com -maxdepth 3 -type d | sort | head -60
```

First-party packages typically:
- Use the app's domain (e.g., `com.debridstream.tv`, `com.movie`)
- Contain UI activities, data models, repositories
- Contain the core business logic

### 6. Identify Architecture (in order)

```bash
# a) Activity/Fragment/ViewModel classes
ls app-name-jadx/sources/com/$app/ui/activity/
ls app-name-jadx/sources/com/$app/ui/fragment/

# b) Data layer
ls app-name-jadx/sources/com/$app/data/

# c) Application class (DI setup, init logic)
cat app-name-jadx/sources/com/$app/$App.java | head -60

# d) Database/entities
ls app-name-jadx/sources/com/$app/database/
```

### 7. Read Key Files in Priority Order

1. **Application class** — initialization, DI, global state
2. **Launcher Activity** (SplashActivity/MainActivity) — navigation entry point
3. **Detail/Movie screen** — main content screen patterns
4. **Player/ExpandedControls** — video player UX
5. **Provider/Resolver** architecture — if scraping-based
6. **Settings/Profile** screens — user management
7. **ViewModel classes** — modern apps use MVVM state patterns
8. **Database/Room entities** — data model design
9. **Network/client setup** — OkHttp, Retrofit, caching
10. **Resources** (strings.xml, styles.xml, colors.xml, themes)

### 8. Extract Key Patterns

For each area, note:
- **Navigation model**: Single activity vs multi-activity, Compose vs XML, tab structure
- **DI framework**: Dagger/Hilt/Koin/manual
- **State management**: RxJava vs StateFlow vs LiveData
- **Image loading**: Glide/Coil/Fresco — caching config
- **Player**: ExoPlayer v2 vs Media3, control customization
- **Caching**: OkHttp cache size, image cache size, policy
- **Auth**: OAuth flows, token storage, Firebase
- **Monetization**: Ad SDKs, subscription SDKs
- **Analytics/Errors**: Firebase, Crashlytics

### 9. Handle Obfuscated Code

When classes use single-letter packages (a, b, c...) or names like `C0637a1`:
- Look for the non-obfuscated entry points (Activities, Application, ViewModels declared in manifest or seen as first-party)
- The manifest's activity/application declarations are NOT obfuscated — they're your Rosetta Stone
- Read the obfuscated class only when it's directly referenced by a known entry point
- Focus on patterns, not every line — the purpose of an obfuscated class can often be inferred from how the non-obfuscated code calls it

**Grep-first discovery strategy for obfuscated APKs**: When hunting for a specific pipeline (image loading, networking, caching, player), do NOT read classes at random hoping to find the target. Instead:
1. Identify string constants unique to the domain — URL patterns (`image.tmdb.org`, `w342`, `w500`, `poster_path`, `fanart.tv`), API base URLs (`api.trakt.tv`, `api.themoviedb.org`), or SDK method calls (`ImageRequest.Builder`, `.data(`, `.memoryCacheKey(`)
2. Search across the entire `sources/` directory filtering out library packages (`androidx/`, `com/google/`, `okhttp3/`, `coil/`)
3. This instantly surfaces the 5-20 files that matter — read those in full, skip the rest
4. Then trace the call chain: find where the string is produced → where it's consumed → where it hits the UI/library boundary
5. For image pipelines specifically: search for Coil/Glide builder patterns first, then work backwards to the data sources

### 9b. Tracing Scraping/Debrid/Addon Pipelines in Obfuscated Streaming Apps

Streaming apps that support debrid services (Real-Debrid, Premiumize, TorBox, AllDebrid, OffCloud) follow one of two architectural patterns. Identify which one early — it determines where the interesting code lives.

**Pattern A — Standard Stremio Protocol (simpler):**
- Addon manifests at `{baseUrl}/manifest.json`
- Streams at `{baseUrl}/stream/{type}/{id}.json`
- Fixed JSON schema: `{ streams: [{ url, infoHash, name, title, behaviorHints }] }`
- Look for: Retrofit `@GET` interfaces, `StreamResponse`/`Stream` models, `StremioAddonClient`-like classes
- Cache layer: multi-resource with per-type TTLs

**Pattern B — Custom API per Scraper (DebridStream-style):**
- Remote config (GitHub JSON) defines each addon's URL template, JSON parse keys, and debrid provider
- Each addon has a custom response format — the config tells the parser which fields to extract
- Look for:
  - A `ScraperAddon` data class with fields: `url`, `jsonResultsKey`, `name`, `sizeAttr`, `sizePattern`, `torrentNameTargetKey`, `httpUrlTargetKey`, `debridProvider`
  - A central orchestrator class that iterates all addons in parallel, routes each result to the right debrid provider
  - A `RemoteConfigLoader` that fetches JSON from GitHub (e.g., `raw.githubusercontent.com/.../config.json`)
  - Per-debrid-provider integration classes (one per provider, with dedicated API calls for unrestrict/check-cache/resolve)

**Tracing technique for Pattern B:**
1. Search for debrid provider names as string constants: `"realdebrid"`, `"torbox"`, `"premiumize"`, `"alldebrid"`, `"offcloud"`, `"debrider"` — the orchestrator routing function branches on these
2. Search for `ScraperAddon` model definition — reveals the config-driven parsing structure
3. Search for `%searchPattern` or `{imdbId}` placeholder in URLs — these are scraper URL templates
4. Trace the orchestrator function: it takes `imdbId` + `type` → builds path → iterates addons → parses each → routes to debrid
5. For each debrid provider, locate its dedicated class by following method calls from the router's `switch`/`if-else` chain

**Pattern C — Web-Scraped Video Hoster Extractors + Embed URLs (Flix Vision / Cinema-like):**

This pattern skips torrents entirely — it scrapes streaming websites for direct video URLs and embeds. Common in apps that offer "free streaming" without debrid.

- **Embed sources first**: Hardcoded embed URL templates (smashystream, vidsrc.xyz, vidsrc.me, autoembed.co) loaded into WebView. Multiple replicas (FLIXVISION1-5) for redundancy
- **Per-site scraper extractors**: Each streaming site gets a dedicated extractor class that:
  1. Fetches the page HTML (JSoup)
  2. Extracts encrypted/protected video URLs (crypto, ROT13, base64 brute-force)
  3. Returns direct `.mp4`/`.m3u8` stream URLs
- **Movie-link scrapers as fallback**: Sites like allmovieland.you, trinstor297undoint.com serve direct playlist URLs by IMDB ID
- **Debrid is optional**: Only used when the user has an account — debrid services resolve file-hoster links (rapidgator, uploaded, etc.) not torrents
- **Private IP backend**: A simple HTTP server (`185.112.144.240`) proxies scrapers and serves metadata paths — no cloud function required
- **Look for**:
  - A `process()`/`fetchLinks()` method per extractor taking `(imdbId, season, episode)` — these are the entry points
  - A central `LinksActivity` or `LinksViewModel` with a switch/loop iterating all extractors and embedding sources
  - AsyncTask-based execution (older apps) or RxJava/Coroutine-based (newer)
  - `BaseExtractor` abstract class with `domain`, `callBack`, `context` fields
  - The label patterns: `"1080p - [GOGOSTREAM] - [DIRECT] - English"`, `"[FLIXVISION2]"`, `"[FVSTREAM 4]"`
- **Resilience strategy**: When one embed domain goes down, they fall through to the next replica URL. Extractor failures are silently caught and logged.

**Common patterns across all three architectures:**
- **ViewModel-based screen state**: `ScraperResultsViewModel` with `_uiState` + `uiState` exposed as `StateFlow`
- **AtomicInteger/AtomicBoolean for cancellation**: `activeRunId`, `currentCancel` — increment-and-check pattern for stale search prevention
- **CopyOnWriteArrayList** for thread-safe result aggregation
- **Shared OkHttp connection pool** with debrid-auth interceptor
- Result model with quality flags (4K, FHD, HEVC, Atmos, TrueHD, HDR, DV, DTS)

### 9c. Dual-APK Comparison (Competitive Audit)

When the user provides TWO APKs and asks how they work / to compare them:

1. **Check both for open source first** — search both app names on GitHub. One may have clean source (clone it, skip jadx), the other may need decompilation. Document which is which in the audit.

2. **Parallelize the data collection:**
   - Decompile the closed-source APK with jadx while simultaneously cloning the open-source repository
   - Read both manifests in parallel — extract activities, permissions, SDKs, player type
   - Map both package structures simultaneously

3. **Deliverable: single side-by-side comparison document** structured as:
   - **App A Identity** + **App B Identity** (package, version, minSdk, developer)
   - **Architecture Comparison Table** — Kotlin vs Java, player, DI, state management, image loading
   - **Screen Map** — activity list for each (find common patterns and unique screens)
   - **Data Flow** — how content gets from source to screen (API vs scraping vs IPTV)
   - **Monetization** — ads, subscriptions, activation codes, or none
   - **Tech Stack Side-by-Side** — every dependency compared
   - **Verdict** — which is the better base to fork/build on, what to copy from each

4. **Include specific verdict guidance**: If one app is open source (clean Kotlin) and the other is a closed-source commercial IPTV app, recommend forking the open source one and porting specific features (IPTV, downloads, ads) from the commercial one.

5. **Cross-reference findings**: When one app has a feature the other doesn't (e.g., live TV, downloads, 89 video extractors), call it out explicitly so the user knows what to combine.

### 9d. Enterprise-Grade Protection (DexProtector / Arxan)

Some commercial APKs use enterprise obfuscation tools (DexProtector by Arxan). These are fundamentally different from ProGuard — static analysis hits hard limits. Recognize and adapt.

**Detection indicators:**
- Application class extends `Application` with `System.loadLibrary("dexprotector")` in `attachBaseContext`
- A `LibTvPlayerApplication`-style JNI bridge class with hundreds of overloaded native methods
- `R.java` class where every resource field is named `h` (JADX INFO comments show original names)
- ALL first-party classes renamed to Unicode characters — SMALI filenames like `ːʤﹶᵷ.smali`
- jadx writes obfuscated classes to `defpackage/AbstractCXXXX.java` but these names don't exist in actual SMALI
- **No readable app-specific strings** in SMALI `const-string` directives (they're in the encrypted DEX string pool)
- Native libraries (`lib/arm64-v8a/libdexprotector.so`) with stripped symbols, encrypted `.text` sections, minimal readable strings

**Protection layers (in order of execution):**

1. **Cert pinning (Java)** — `attachBaseContext` gets package signature, SHA-256 hashes it, compares against hardcoded value. Throws `RuntimeException` on mismatch.
2. **Native library init** — `libdexprotector.so` loaded via `System.loadLibrary`. `.init_array` + `JNI_OnLoad` may independently verify APK integrity, CRC-check DEX files, or detect tampering.
3. **String pool encryption** — All string constants in the DEX data section are encrypted. The native lib decrypts them at runtime — no `const-string` in SMALI contains readable app strings.
4. **Class renaming** — Every class and method renamed to garbage Unicode. No meaningful identifiers survive.

**Workflow for DexProtector-protected APKs:**

1. **Decompile with jadx** as usual — the manifest, resources, and application class are our only Rosetta Stone. Read them first.
2. **Identify the cert hash** from `ProtectedTvPlayerApplication`-equivalent — patch `b()` method to no-op.
3. **Use baksmali directly on DEX files** (not apktool for rebuild). Apktool rebuild fails on DexProtector APKs due to duplicate resources.
   ```bash
   unzip app.apk classes.dex classes2.dex classes3.dex
   java -jar baksmali.jar d classes.dex -o smali_out/
   # Edit the SMALI to patch cert check
   java -jar smali.jar a -o classes.dex smali_out/
   ```
4. **Inject patched DEX back into original APK** using Python `zipfile` — avoid apktool's resource rebuild step entirely.
5. **Strip META-INF**, re-sign with new key.
6. **Native lib risk**: Even with Java cert check patched, `libdexprotector.so` may have its own verification. Test the patched APK — if it crashes at startup, the native lib caught it. In that case, a Frida/runtime approach is needed to dump decrypted state.

**Premium bypass for server-side validated apps:**
- Apps with Parse SDK + Google Play Billing + server-side activation (like TiviMate) validate premium status on a Parse backend.
- Static SMALI patching alone can't bypass server checks. Strategy:
  1. **Frida hook** `ParseUser.getBoolean()` / `SharedPreferences.getBoolean()` to force premium=true
  2. **Intercept** network calls to activation endpoint and return valid response
  3. **Dump runtime-decrypted DEX** via Frida, repack without DexProtector, patch premium check in clean DEX

### 9e. LLM-Assisted Deobfuscation (Androidmeda)

When ProGuard/R8 max obfuscation (single-letter package names) defeats jadx, use an LLM-powered deobfuscation tool like Androidmeda. It sends decompiled Java files to an LLM which renames classes/variables/methods to meaningful names and annotates vulnerabilities.

**Install:**
```bash
git clone --depth 1 https://github.com/In3tinct/Androidmeda.git
cd Androidmeda
python3 -m venv .venv && source .venv/bin/activate
pip3 install -r requirements.txt
```

**Usage with local models (OpenAI-compatible API like llama-swap):**
```bash
export API_KEY=***
export OPENAI_BASE_URL="http://127.0.0.1:9292/v1"
python3 androidmeda.py --llm_provider openai --llm_model qwythos-9b-mtp-q6 --output_dir /tmp/deobf-out --source_dir "/path/to/target/" --save_code true --thread_size 1
```

**Important:**
- Send app-specific subdirectories only (omit `androidx/`, `okhttp3/`, `com/google/`, `kotlin/`)
- 9B models may fail JSON parsing on files >5K tokens; 35B is more reliable
- Grep-first string discovery is primary; LLM deobfuscation is enrichment
- **Exit code 2 with no output means LLM timeout, not script failure** — check if the model is warm
- **Model must be warm** (first request after swap takes 30–60s just for model loading into VRAM)
- **Use `--thread_size 1`** — essential for single-model setups; higher values queue requests and waste tokens on duplicated context
- **`API_KEY` env var** is checked by the script (line 178), not `OPENAI_API_KEY` — set both to avoid confusion
- **OpenAI provider uses synchronous client from async code** — this blocks the event loop. With fast models (<10s response) this is fine. With slow models (>30s), the script may appear to hang. Set `max_tokens` conservatively or prefer a faster model
- **Break large packages (200+ files) into batches** — a 234-file package takes ~2–4 hours with a 35B model at 25–60s per file. Plan accordingly or only target the most critical packages

### 9h. Extracting Surviving Constants from Heavily Obfuscated Code

When class names, method names, and strings are destroyed by ProGuard + custom obfuscation (like TiviMate v5.3.3 with DexProtector), the usable code lives in **surviving constants and patterns**. These survive because they're either:
- Java `final static Pattern` fields (regex patterns)
- Integer/long constants passed to framework methods
- String constants in library calls that can't be encrypted
- Library import/class references that survive renaming

**Systematic constant extraction strategy:**

```bash
# 1. Java regex patterns - these survive as Pattern.compile(string)
grep -r "Pattern.compile" . --include="*.java" | grep -v "/androidx/\|/google/\|/firebase/" | head -40

# 2. HTTP headers / user-agent strings
grep -r '"User-Agent"\|"Accept"\|\"Content-Type"\|"Authorization"' . --include="*.java" | grep -v "/androidx/" | head -20

# 3. Millisecond timing constants (timeouts, buffer sizes, intervals)
grep -r "5000\|10000\|15000\|30000\|60000\|120000\|300000" . --include="*.java" | grep -v "/androidx/" | grep -v "firebase\|gms\|protobuf" | head -30

# 4. ExoPlayer/Media3 wrapper classes (search by framework package name)
grep -rl "ExoPlayer\|Media3\|SimpleExoPlayer\|LoadControl\|DefaultLoadControl" . --include="*.java" | grep -v "/androidx/" | head -10

# 5. Non-obfuscated model classes (your app's unique package names)
find . -path "*xtream*" -o -path "*Xtream*" -o -name "*UserInfo*" -o -name "*ServerInfo*" 2>/dev/null | head -20

# 6. Numbered constants (message codes, state enums)
grep -r "case 1:\|case 2:\|case 3:" . --include="*.java" | grep "//\|switch\|Message" | grep -v "/androidx/" | head -20
```

**What to grep for by domain:**

| Domain | Search Pattern | What You'll Find |
|--------|---------------|------------------|
| **M3U/HLS parsing** | `Pattern.compile`, `#EXT`, `EXTINF` | Regex patterns for playlist parsing |
| **Player wrapper** | `LoadControl`, `minBufferMs`, `Media3` | Buffer sizes, player config |
| **EPG timeline** | `pixelsPerMinute\|60000\|3600000` | Time-to-pixel conversion constants |
| **HTTP/API** | `"User-Agent"`, `"Accept"`, `xtream` | API endpoint patterns |
| **DRM/License** | `parse\|firebase\|billing` | Provider/SDK detection |
| **Image pipeline** | `Glide\|Coil\|imageLoader`, `.data(` | Image loading strategy |
| **State machine** | case `\d+:`, `Message.what`, `switch` | Player/service state machines |

**Organization strategy for extracted findings:**

Create one reference file per component domain:

```
references/tivimate-m3u-parser-regex.txt        # All regex patterns
references/tivimate-player-buffering-config.txt  # Buffer/player config
references/tivimate-xtream-codes-api.txt         # API data models
references/tivimate-epg-rendering.txt            # EPG algorithm
references/tivimate-app-architecture.txt         # Overall architecture
```

Each file should list the exact extracted values with source file references where possible. The SUMMARY file maps what's usable vs too obfuscated.

### 9i. APK Download Strategy — Anti-Bot Workarounds

Major APK download sites block automated downloads with Cloudflare/Uptodown anti-bot:

| Site | Protection | Status |
|------|-----------|--------|
| APKMirror | Cloudflare JS challenge | Blocked via curl |
| Uptodown | Custom anti-bot | Blocked via curl |
| tivimate.com | Cloudflare | Blocked via curl |
| **Aptoide** | No bot protection | **Direct DL works** |
| FileHippo | No bot protection | Direct DL works |

**Aptoide pool URL format** (working fallback):
```
https://pool.apk.aptoide.com/aptoide-web/{pkg}-{versioncode}-{hash}.apk
```

Extract from the Aptoide app page (e.g. `https://{app}.en.aptoide.com/app`):
```bash
curl -sL -A "Mozilla/5.0" "https://{app}.en.aptoide.com/app" | grep -oP 'https?://[^"]+\.apk[^"]*'
```

This returns a direct pool URL. Download with:
```bash
curl -L -o app.apk "<pool_url>"
```

### 9f. Probing Firebase/Firestore Backend Access

After identifying the app's Firebase project, verify whether collections are publicly accessible:

```bash
# Find project ID
strings app.apk | grep -i "firebase\\|gmp" | grep -oP '[a-z]+-[a-z0-9]+\\.firebase' | sort -u

# Test Firestore REST
curl -s "https://firestore.googleapis.com/v1/projects/{id}/databases/(default)/documents/"
# 403 PERMISSION_DENIED = locked (good)

# Test with API key
curl -s "https://firestore.googleapis.com/v1/projects/{id}/databases/(default)/documents/?key=$API_KEY"
# 403 API_KEY_ANDROID_APP_BLOCKED = key SHA-1 locked (good)

# Anonymous auth
curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=$API_KEY" -H "Content-Type: application/json" -d '{"returnSecureToken": true}'

# Collect Firestore collections from decompiled code
grep -rohP 'collection\\("[^"]+"' jadx-out/sources/ | sort -u
```
Common streaming app collections: `debridKeys`, `accountSync`, `catalogs`, `users`, `profiles`.

### 9g. Full Backend Architecture Mapping from Strings

When class names are destroyed by ProGuard/R8 but strings survive, map the entire backend by collecting ALL hardcoded strings:

| Search Pattern | What It Reveals |
|---|---|
| `https?://` | All API endpoints, CDN hosts, remote config URLs |
| `collection("` | Firestore collection names |
| `firebase\|gmp` | Firebase project ID |
| `trakt\|tmdb\|justwatch\|opensubtitles` | Media metadata providers |
| `realdebrid\|torbox\|premiumize\|alldebrid` | Debrid provider integrations |
| `revenuecat\|entitlement\|purchases` | Subscription/IAP SDK |
| `raw.githubusercontent` | Remote config/storage URLs |
| `goog_\|appl_\|rc_` | RevenueCat SDK keys |
| `AIzaSy` | Firebase/Google API keys |

```bash
cd jadx-out/sources/
# Custom backend endpoints (not third-party)
grep -rohP '"https?://[^"]*(?:api|auth|admin|portal|backend)[^"]*"' . | grep -vE "google|github|trakt|tmdb|raw\\." | sort -u
# Firestore collections
grep -rohP 'collection\\("[^"]+"' . | sort -u
```

### Patching Binary AndroidManifest.xml (AXML)

When apktool can't rebuild (DexProtector duplicate resources), you must patch the binary AndroidManifest.xml directly. AXML uses a structured binary format with a string pool and typed attribute values.

**Common patching scenarios:**
- Change `extractNativeLibs` from `false` to `true` (fixes INSTALL_FAILED on non-`zipalign`ed APKs)
- Change `debuggable` from `false` to `true`
- Change `backup` settings

**Workflow:**

1. Extract the binary manifest from the original APK:
   ```bash
   unzip original.apk AndroidManifest.xml
   ```

2. Write a Python AXML parser to find the target attribute and patch its typed value:
   ```python
   import struct
   
   with open('AndroidManifest.xml', 'rb') as f:
       data = bytearray(f.read())
   
   # AXML string pool: string index for the attribute name
   # You find this by parsing the StringPool chunk (type 0x0001)
   # For extractNativeLibs, search the string pool to get its index
   
   # The attribute structure (20 bytes per attr):
   #   [0-3]  namespace URI (string index, -1 = none)
   #   [4-7]  attribute name (string index)
   #   [8-11] raw value (string index)
   #   [12-13] Res_value.size (=8)
   #   [14]    reserved
   #   [15]    dataType (0x12 = boolean)
   #   [16-19] data (0 = false, 0xFFFFFFFF = true)
   
   attr_name_idx = <INDEX>  # string pool index of target attribute
   
   for i in range(len(data) - 20):
       name = struct.unpack('<I', data[i:i+4])[0]
       if name == attr_name_idx:
           dtype = data[i+11] if i+11 < len(data) else 0
           if dtype == 0x12:  # boolean typed value
               # Patch from false (0) to true (0xFFFFFFFF)
               struct.pack_into('<I', data, i+12, 0xFFFFFFFF)
               break
   
   with open('AndroidManifest.xml', 'wb') as f:
       f.write(data)
   ```

3. Inject the patched manifest back into the APK (alongside patched DEX), strip META-INF, re-sign.

**Finding the string pool index:** Parse the AXML StringPool chunk (type 0x0001) which starts at offset 8 (after the 8-byte XML container header). The string pool header is:
```
[0-1]  chunk_type (0x0001)
[2-3]  header_size (28)
[4-7]  chunk_size
[8-11] string_count
[12-15] style_count
[16-19] flags
[20-23] strings_start (relative to chunk start)
[24-27] styles_start
[28...] string offset array (4 bytes per string)
```
After the offset array, the `strings_start` area contains UTF-16LE null-terminated strings. Iterate until you find the target attribute name and record its index.

**Simpler brute-force approach** (when you know the string index from prior analysis or a common value): Search for the 4-byte `name_index` as a little-endian integer in the raw byte array, then verify the adjacent byte is `0x12` (boolean data type) and patch:

```python
import struct
with open('AndroidManifest.xml', 'rb') as f:
    data = bytearray(f.read())

target_attr = struct.pack('<I', 21)  # replace with the actual string index

for k in range(len(data) - 20):
    if data[k+4:k+8] == target_attr:       # name_index matches
        if data[k+11] == 0x12:              # boolean typed value
            struct.pack_into('<I', data, k+12, 0xFFFFFFFF)  # false → true
            break

with open('AndroidManifest.xml', 'wb') as f:
    f.write(data)
```

## Pitfalls

- **jadx errors are normal**: `--show-bad-code` may produce 20-60 errors. Most classes decompile correctly. Only worry if >30% of files have errors.
- **Obfuscation ≠ security**: Heavily obfuscated apps (single-letter packages) still reveal their architecture through the manifest, application class, and ViewModel entry points.
- **Check the manifest for hidden screens**: Some activities are in the manifest but not linked in the UI navigation — these are often debug, admin, or test screens.
- **Strings may be obfuscated**: Look for `Deobfuscator` or native libraries loading strings. Don't assume string resources contain the actual app strings.
- **Don't read library code**: Packages like `com/facebook/*`, `com/google/*`, `okhttp3/*`, `androidx/*` are dependencies, not app code. Focus on the app's own package.
- **Read files with read_file, not cat**: read_file provides line numbers and handles large files automatically.
- **Debrid stream apps layer their scraping**: Don't confuse the addon/scraper layer (fetching torrent/magnet results) with the debrid resolution layer (converting torrents to direct URLs). In Pattern B apps, these are separate packages — the addon layer calls external APIs, the debrid layer has per-provider SDK-like integration.
- **Remote config changes behavior**: Pattern B apps load addon lists from remote JSON. The addons you see in decompiled code may differ from what the app uses at runtime. The config JSON defines which addons are active, their URLs, and parse mappings.
- **User wants 1:1 copy, not "inspired by"**: When a user says "copy what X does" or "port it 1:1," they want the exact data flow, component structure, and field names — not a reimagined version. Don't refactor their architecture or rename things unless they explicitly ask. Show them the reference file, point to the exact DebridStream class being mirrored, and make the implementation as close to the original as possible. An "adapted" version that renames fields and shuffles responsibilities will get corrected.
- **Pattern C apps have Hindi/Dubbed direct layers**: Many web-scraping streaming apps include a separate Hindi/Dubbed direct link pipeline alongside the primary English source. Look for `pref_show_hindi_dubbed_direct`, `GetStreamLinkHindi()`, `/playlist/{file}.txt` endpoints — these produce direct 1080p playlists from movieland-type services that bypass all scraping complexity. They're a separate code path, not a subset of the main extractor loop.
- **Clean source vs decompiled output quality**: When comparing two apps where one has clean source and the other is decompiled, note that the decompiled code will have obfuscated variable names, anonymous class patterns (`new View.OnClickListener(this) { ... }`), and won't be copy-pasteable. The clean source IS copy-pasteable. Flag this in the audit — the user should fork the clean source and manually port patterns from the decompiled one.
- **Streaming apps have two revenue models**: Commercial IPTV apps (like Netfly TV) monetize via subscriptions + ads + activation codes. Free VOD scraping apps (like Streamflix) have no monetization at all. If the goal is a commercial product, you need to combine both patterns.
- **DexProtector blocks apktool rebuild**: Enterprise-protected APKs (DexProtector) produce 200+ duplicate layout files during apktool decode, causing rebuild failures. Work around this by extracting DEX files directly with `unzip`, patching at the SMALI level via baksmali/smali, and injecting the modified DEX back into the original APK with Python `zipfile`. Skip apktool entirely for the rebuild step.
- **DexProtector's native lib has its own integrity checks**: Even after patching the Java-level cert check, `libdexprotector.so` may crash if it detects DEX tampering via CRC or hash. The only reliable way to verify is to install the patched APK and test. If it crashes, a Frida runtime approach is needed to dump the decrypted state before repacking without the protector.
- **All app strings are absent from SMALI in DexProtector apps**: Don't search for readable strings (premium, URLs, API keys) in the obfuscated SMALI — they won't exist. The string pool in the DEX is encrypted and decrypted at runtime by the native library. Grep-based search strategies for app-specific strings will find nothing; focus on structural patterns (method signatures, class hierarchies, resource ID references) instead.
- **Native libs MUST be stored uncompressed in repacked APKs**: Android's `PackageManager` requires all `.so` files in `lib/` and all `.dex` files to be stored uncompressed (`ZIP_STORED`). If you repack an APK with Python `zipfile` using `ZIP_DEFLATED`, you'll get `INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2`. Always detect and exempt native libs and DEX files from compression:
  ```python
  is_native = arcname.startswith('lib/') and arcname.endswith('.so')
  is_dex = arcname.endswith('.dex')
  compress = zipfile.ZIP_STORED if (is_native or is_dex) else zipfile.ZIP_DEFLATED
  ```

**INSTALL_FAILED due to extractNativeLibs=false + alignment**: Even with native libs stored uncompressed, `INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2` occurs when the manifest has `android:extractNativeLibs="false"`. This flag tells Android to mmap .so files directly from the APK instead of extracting them — which requires **page-aligned (4096-byte) offsets** in the zip. Only `zipalign -p` can guarantee this. When zipalign isn't available, **patch `extractNativeLibs` to `true` in the binary AndroidManifest.xml** (see section "Patching Binary AndroidManifest.xml (AXML)" below).
- **jarsigner -digestalg SHA1 is blocked**: Modern Android (API 28+) rejects APKs signed with SHA-1. Use `-sigalg SHA256withRSA -digestalg SHA-256`.

## Verification

After analysis, confirm:
- [ ] Manifest fully read (all activities, services, receivers mapped)
- [ ] First-party packages identified vs library code
- [ ] Application class initialization flow understood
- [ ] Navigation model identified (single vs multi-activity)
- [ ] State management framework identified (RxJava, Flow, LiveData)
- [ ] Database schema extracted (entities, relations)
- [ ] Network layer pattern documented (Retrofit, OkHttp, interceptors)
- [ ] Player implementation identified (ExoPlayer v2/v3, Media3)
- [ ] Monetization model documented (ads/subscription)
- [ ] Debrid/third-party integrations identified
- [ ] For obfuscated APKs: surviving constants extracted via grep (Pattern.compile, numeric values, HTTP headers, library imports)
- [ ] Surviving constants organized into per-component reference files
- [ ] APK download source documented (Aptoide fallback if primary source blocked)
- [ ] All deliverable documents produced per user request
- [ ] For dual-APK comparison: open source checked for both, parallel data collection done, side-by-side table produced, verdict given on which to fork

## Related References

- `references/debridstream-firetv-image-pipeline.md` — Complete DebridStream FireTV image pipeline analysis (normalizer, Coil config, data flow, NexStream porting gaps)
- `references/debridstream-v3-kotlin-rewrite.md` — **New (Jun 2026):** DebridStream v3 native Kotlin/Compose rewrite analysis: Firebase-only backend, Firestore collections, RevenueCat bypass, GitHub config-driven catalog, v2→v3 migration.
- `references/debridstream-firetv-scraping-architecture.md` — DebridStream source scraping architecture: remote config, custom-API addons, debrid provider routing, comparison with standard Stremio protocol approach
- `references/cinema-hd-debrid-stream-reference-audit.md` — Competitor APK analysis patterns
- `references/nuviotv-hybrid-rebrand-approach.md` — NuvioTV source-based codebase adoption lessons
- `references/tivimate-m3u-parser-regex.txt` — **New (Jul 2026):** 50+ Java regex patterns extracted from TiviMate v5.3.3's HLS/M3U playlist parser. Covers EXTINF, bandwidth, codecs, resolution, encryption keys, SCTE date ranges, partial segments, delta updates, and custom X- tags.
- `references/tivimate-player-buffering-config.txt` — **New (Jul 2026):** ExoPlayer buffer tuning values extracted from TiviMate (minBuffer 50s, maxBuffer 100s, startup 2.5s, rebuffer 5s) plus the 30+ message handler command map and OkHttp 5.4.0 config.
- `references/tivimate-xtream-codes-api.txt` — **New (Jul 2026):** Xtream Codes API data models (UserInfo, ServerInfo, CatchupData field names) and all API endpoint patterns (player_api.php actions for live/VOD/series/EPG).
- `references/tivimate-epg-rendering.txt` — **New (Jul 2026):** EPG timeline rendering algorithm: time-to-pixel formula, program overlap handling, live indicator, refresh cycle.
- `references/tivimate-app-architecture.txt` — **New (Jul 2026):** TiviMate v5.3.3 architecture layers (network, data, player, UI), permissions, detected libraries (Media3, OkHttp 5.4.0, Moshi, Glide, Parse SDK, SMBJ, Firebase).
- `references/flix-vision-full-audit.md` — **New (May 2026):** Full Flix Vision v3.6.3r audit: API keys, all 7 video hoster extractors with URLs, embed URL patterns (FLIXVISION1-5), movie link scrapers, UI assets, private IP backend.
- `references/porting-debridstream-scraper-pipeline.md` — 1:1 port of DebridStream's config-driven scraper pipeline: ScraperAddonConfig, ScraperResponseParser, ScraperQualityDetector (C1125i regex mirror), DebridRouter, StreamScraperEngine, and Torrentio endpoint verification (May 2026)
- `references/multi-apk-extraction-workflow.md` — **New (June 2026):** Batch extraction from multiple competitor APKs into an organized engine directory (debrid/stremio/trakt/providers/extractors). Covers SCP from CachyOS, parallel jadx, triage by app type, system extraction checklists for STRMR/ONstreamz/Netfly/Streamflix, and pitfalls (ProGuard, Moshi adapters, Room _Impl).
- `references/netfly-tv-iptv-architecture.md` — **New (Jun 2026):** Netfly TV IPTV/VOD architecture: 60 signed API endpoints, CMS config pattern, IJKPlayer setup, Flutter hybrid app, ad SDKs (AdMob + Pangle v8.1.0.3), 34-activity screen map, download service, auth flow
- `references/smali-patching-subscription-bypass.md` — SMALI-level patching of ProGuard-obfuscated APKs for subscription bypass. RevenueCat entitlement detection, coroutine state machine patching, reassemble/inject/sign workflow, signing pitfalls.
- `references/tivimate-dexprotector-analysis.md` — TiviMate 5.3.3 DexProtector analysis: cert pinning bypass, native library dissection, premium system (Parse + Play Billing + 5-device activation), string encryption patterns, DEX-level patching workflow.