# Pro / OSS split (cast-core + cast-pro AAR)

Design notes for a commercial build without forking the cast protocol. **Not implemented yet** — complements [COMMERCIAL.md](COMMERCIAL.md) (dev-gated Play Billing placeholders).

---

---

---

---

---

## Table of contents

<!-- toc -->
- [Goals](#goals)
- [Entitlements API (in core)](#entitlements-api-in-core)
- [Module layout (Gradle)](#module-layout-gradle)
- [Play Billing flow](#play-billing-flow)
- [Build & release](#build-release)
- [Migration steps (suggested order)](#migration-steps-suggested-order)
- [Relation to sender resolution test](#relation-to-sender-resolution-test)
<!-- /toc -->

**Documentation index:** [README.md](README.md)

---

## Goals

- **OSS (`cast-core`)** — protocol, UDP/TCP transport, receiver, sender engine, crash reporter client, settings model. Publishable repo for trust and forks.
- **Closed (`cast-pro` AAR)** — entitlements, license check, optional premium codecs/UI, Play Billing glue. Distributed via private Maven or Play App Bundle module (not on public Maven).
- **Single APK** on Play: `app` depends on `cast-core` + `cast-pro` at compile time; F-Droid / OSS build uses `cast-core` only with stub entitlements.

## Entitlements API (in core)

Keep a thin interface in core so free code never imports Play Billing:

```java
public interface CastEntitlements {
    boolean isPro(Context ctx);
    int maxReceivers();
    boolean allowsResolution(CastSettings.Resolution r);
    boolean allowsStreamProtection();
}
```

- **Default (OSS):** `FreeCastEntitlements` — e.g. max 1 receiver, cap outgoing long edge at 480 unless dev override, FEC/NACK off or dev-only.
- **Pro:** `ProCastEntitlements` in AAR — reads signed license / Play purchase cache, returns higher limits.

Call sites (minimal first pass):

| Area | Gate |
|------|------|
| `CastTuningEngine` / sender encode | `allowsResolution()` |
| Multi-receiver connect | `maxReceivers()` |
| Stream protection UI | `allowsStreamProtection()` |
| Developer resolution UI modes | Always allowed when `BuildConfig.DEBUG` or `AppPreferences` dev flags |

`CommercialFeatures` stays **developer toggles** for ads/IAP/Play review; **pro entitlement** is runtime product state from Billing + AAR.

## Module layout (Gradle)

```
cast-core/          # android library, published OSS
cast-pro/           # android library, proprietary, implementation project OR maven coords
app/                # application
```

`app/build.gradle`:

- `implementation project(':cast-core')`
- `implementation 'com.foxx.androidcast:cast-pro:…'` (release) or `implementation project(':cast-pro')` (internal)
- Optional `ossRelease` flavor: `implementation project(':cast-core')` only + `FreeCastEntitlements`

**ServiceLoader or manifest meta-data** to bind `CastEntitlements` implementation avoids `compileOnly` reflection hacks.

## Play Billing flow

1. User buys SKU (see `InAppPurchaseCoordinator.SKU_PREMIUM`).
2. Pro AAR verifies purchase token, caches entitlement locally (encrypted prefs or Play Library `BillingClient` + acknowledge).
3. `CastEntitlements` refreshes; UI shows unlocked resolutions / receivers.
4. Offline grace period (e.g. 7 days) documented in product terms.

## Build & release

- Version **cast-core** and **cast-pro** independently; app pins both in `gradle/libs.versions.toml`.
- CI: OSS job builds `cast-core` + F-Droid APK; private job signs `cast-pro` and release APK.
- Do not embed license secrets in OSS; pro AAR may contain obfuscated verification only.

## Migration steps (suggested order)

1. Extract `CastEntitlements` + `FreeCastEntitlements` in app (no module split yet).
2. Wire gates at encode + multi-receiver (behind dev “simulate free tier” toggle).
3. Create `:cast-core` library — move `network/`, `cast/`, shared settings (large refactor).
4. Add `:cast-pro` with `ProCastEntitlements` + Billing bridge.
5. Play Console product + privacy policy ([COMMERCIAL.md](COMMERCIAL.md)).

## Relation to sender resolution test

Developer **Sender resolution UI mode** ([`SenderResolutionUi`](../app/src/main/java/com/foxx/androidcast/sender/SenderResolutionUi.java)) is for UX experiments only. Production free tier should still enforce caps in `CastTuningEngine` via entitlements, not by hiding spinner entries alone.
