diff --git a/.claude/conventions.yml b/.claude/conventions.yml new file mode 100644 index 00000000..a9348dd1 --- /dev/null +++ b/.claude/conventions.yml @@ -0,0 +1,39 @@ +conventions: + - id: adapter-structure + description: > + Each network adapter lives under Adapters/{Network}/IS{Network}Adapter/ + as an Android Library module. All adapters extend AbstractAdapter and + implement INetworkInitCallbackListener. Separate listener classes for + each ad format's load and play events. + applies_to: "Adapters/**/*.{java,kt}" + + - id: naming-conventions + description: > + Adapter class: IS{Network}Adapter. Listener classes follow + IS{Network}{Format}Listener pattern (e.g., ISVungleRewardedVideoListener). + Java is the primary language; Kotlin used only for Pangle and Yahoo + adapters. Standard Android/Java naming conventions. + applies_to: "Adapters/**/*.{java,kt}" + + - id: ad-format-support + description: > + All adapters must support three formats: Rewarded Video, Interstitial, + and Banner. Each format has distinct load and play listener classes. + The AbstractAdapter base class provides shared format registration + and lifecycle management. + applies_to: "Adapters/**/*.{java,kt}" + + - id: version-management + description: > + Adapter versions are tracked per network. The adapter version and + underlying SDK version are declared in the adapter's build.gradle. + Version history is maintained in adapter-level documentation. + applies_to: "**/build.gradle" + + - id: aar-build-output + description: > + The custom Gradle task createAAR builds release AAR artifacts and + places them in the ReleaseCandidates/ directory. This is the primary + build command: ./gradlew createAAR. AAR files are the distribution + format for adapters. + applies_to: "**/build.gradle" diff --git a/.claude/knowledge/adrs/001-abstract-adapter-pattern.md b/.claude/knowledge/adrs/001-abstract-adapter-pattern.md new file mode 100644 index 00000000..9149b4d6 --- /dev/null +++ b/.claude/knowledge/adrs/001-abstract-adapter-pattern.md @@ -0,0 +1,22 @@ +# ADR-001: AbstractAdapter Base Class Pattern + +## Status +Accepted + +## Context +Unity LevelPlay (ironSource) mediates ads from 10 network SDKs on Android. Each adapter must handle SDK initialization, ad format registration, and lifecycle management. Significant boilerplate is shared across all adapters. + +## Decision +All network adapters extend the `AbstractAdapter` base class provided by the ironSource SDK: +- `AbstractAdapter` provides shared initialization lifecycle and format registration +- Each adapter implements `INetworkInitCallbackListener` for initialization callbacks +- The adapter class (`IS{Network}Adapter`) overrides methods for each supported ad format +- Initialization, ad loading, and ad display follow the template method pattern + +## Consequences +- Consistent adapter structure across all 10 networks +- Shared initialization and lifecycle logic reduces duplication +- Adapters focus on network-specific integration code +- AbstractAdapter updates (from ironSource SDK) affect all adapters +- Must keep adapter implementations compatible with the base class contract +- New ad formats require AbstractAdapter support before adapters can implement them diff --git a/.claude/knowledge/adrs/002-listener-separation.md b/.claude/knowledge/adrs/002-listener-separation.md new file mode 100644 index 00000000..d67d184b --- /dev/null +++ b/.claude/knowledge/adrs/002-listener-separation.md @@ -0,0 +1,22 @@ +# ADR-002: Separate Load and Play Listeners Per Format + +## Status +Accepted + +## Context +Each ad format (Rewarded Video, Interstitial, Banner) has distinct lifecycle events. Some events occur during ad loading (success, failure) while others occur during ad playback/display (impression, click, close, reward). Combining these in the adapter class creates large, unwieldy files. + +## Decision +Each ad format has two separate listener classes: +- **Load Listener** (`IS{Network}{Format}LoadListener`): Handles ad load success and failure callbacks +- **Play/Show Listener** (`IS{Network}{Format}Listener`): Handles ad display events (show, click, close, reward) + +These listener classes are instantiated by the adapter and registered with the network SDK for their respective lifecycle phases. + +## Consequences +- Clean separation between loading and display concerns +- Each listener class has a focused, single responsibility +- Adapter class orchestrates listeners but delegates callback handling +- More files per adapter (2 listeners x 3 formats = 6 listener classes) +- Listeners must hold references to the adapter or mediation callback interfaces +- Easier to test individual lifecycle phases in isolation diff --git a/.claude/knowledge/adrs/003-create-aar-task.md b/.claude/knowledge/adrs/003-create-aar-task.md new file mode 100644 index 00000000..5e7b934f --- /dev/null +++ b/.claude/knowledge/adrs/003-create-aar-task.md @@ -0,0 +1,23 @@ +# ADR-003: Custom Gradle createAAR Build Task + +## Status +Accepted + +## Context +Adapter libraries need to be distributed as AAR (Android Archive) files for integration by publishers. The standard Gradle `assembleRelease` task produces AARs in individual module build directories, but a centralized output location simplifies release management and distribution. + +## Decision +A custom Gradle task `createAAR` is defined that: +1. Builds all adapter modules in release configuration +2. Copies the resulting AAR files to a centralized `ReleaseCandidates/` directory +3. Names AAR files with adapter name and version for clarity + +The build command is: `./gradlew createAAR` + +## Consequences +- Single command builds all adapters for release +- Centralized output directory simplifies artifact collection +- AAR naming convention makes version identification easy +- ReleaseCandidates/ directory serves as the staging area for distribution +- Custom task must be maintained as adapters are added or removed +- Developers use this task instead of standard `assembleRelease` for releases diff --git a/.claude/knowledge/computed/adapter-class-index.md b/.claude/knowledge/computed/adapter-class-index.md new file mode 100644 index 00000000..5659dc1b --- /dev/null +++ b/.claude/knowledge/computed/adapter-class-index.md @@ -0,0 +1,56 @@ +# LevelPlay (ironSource) — Vungle Android Adapter Class Index + +## Adapter Entry Point + +| Class | Superclass | Package | +|-------|-----------|---------| +| `VungleAdapter` | `AbstractAdapter` | `com.ironsource.adapters.vungle` | + +**Initialization**: `init(activity, appKey, userId)` → calls `VungleAds.init(context, appKey)` +**Network Key**: `"Vungle"` (registered in ironSource mediation) + +## Format Classes + +| Format | Class | Listener Interface | +|--------|-------|--------------------| +| Interstitial | `VungleInterstitialAdapter` | `InterstitialAdListener` | +| Rewarded | `VungleRewardedVideoAdapter` | `RewardedAdListener` | +| Banner | `VungleBannerAdapter` | `BannerAdListener` | + +## Listener Classes (6 total) + +| Listener | Delegates For | Key Callbacks | +|----------|--------------|---------------| +| `VungleInterstitialAdListener` | Interstitial load | `onAdLoaded`, `onAdFailedToLoad` | +| `VungleInterstitialAdShowListener` | Interstitial show | `onAdStart`, `onAdEnd`, `onAdClicked`, `onAdImpression` | +| `VungleRewardedAdListener` | Rewarded load | `onAdLoaded`, `onAdFailedToLoad` | +| `VungleRewardedAdShowListener` | Rewarded show | `onAdStart`, `onAdEnd`, `onAdClicked`, `onAdRewarded` | +| `VungleBannerAdListener` | Banner load | `onAdLoaded`, `onAdFailedToLoad` | +| `VungleBannerAdShowListener` | Banner show | `onAdImpression`, `onAdClicked` | + +## Callback Mapping (Vungle → LevelPlay) + +| Vungle Callback | LevelPlay Callback | Context | +|----------------|-------------------|---------| +| `onAdLoaded(ad)` | `onAdOpened(adInfo)` / `onAdReady()` | Load success | +| `onAdFailedToLoad(ad, error)` | `onAdLoadFailed(error)` | Load failure | +| `onAdStart(ad)` | `onAdShowSucceeded(adInfo)` | Fullscreen shown | +| `onAdImpression(ad)` | `onAdVisible(adInfo)` | Impression tracked | +| `onAdClicked(ad)` | `onAdClicked(adInfo)` | Click | +| `onAdEnd(ad)` | `onAdClosed(adInfo)` | Fullscreen closed | +| `onAdRewarded(ad)` | `onAdRewarded(adInfo)` | Reward granted | +| `onAdFailedToPlay(ad, error)` | `onAdShowFailed(error)` | Show failure | + +## Bidding Support + +- Implements `INetworkBiddingProvider` interface +- `collectBiddingData(context, params)` → calls `VungleAds.getBiddingToken(context)` +- Token passed to ironSource auction, bid payload returned via `serverData` + +## Key Patterns + +1. **Separate load/show listeners**: Each format has distinct listener for load vs show phase +2. **AdInfo wrapping**: Vungle ad objects wrapped into ironSource `AdInfo` for callback forwarding +3. **Singleton VungleAds**: Single initialization, placement IDs from server data +4. **Banner size mapping**: ironSource `ISBannerSize` → Vungle `BannerAdSize` (BANNER, MREC, LEADERBOARD) +5. **Privacy**: GDPR consent, CCPA via `VunglePrivacySettings`, COPPA via `setCoppa()` diff --git a/.claude/knowledge/computed/churn-leaders.md b/.claude/knowledge/computed/churn-leaders.md new file mode 100644 index 00000000..c9a7c631 --- /dev/null +++ b/.claude/knowledge/computed/churn-leaders.md @@ -0,0 +1,16 @@ +# Churn Leaders +# Auto-generated — do not edit manually + +## Files With Most Lines Changed (last 90 days) + +| Rank | File | Lines Added | Lines Removed | Net | +|------|------|------------|--------------|-----| +| - | `CLAUDE.md` | +40 | -0 | 40 | +| - | `.claude/conventions.yml` | +39 | -0 | 39 | +| - | `.claude/review-rules.yml` | +29 | -0 | 29 | +| - | `.claude/knowledge/adrs/003-create-aar-task.md` | +23 | -0 | 23 | +| - | `.claude/knowledge/adrs/002-listener-separation.md` | +22 | -0 | 22 | +| - | `.claude/knowledge/adrs/001-abstract-adapter-pattern.md` | +22 | -0 | 22 | + +--- +_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_ diff --git a/.claude/knowledge/computed/co-change-clusters.md b/.claude/knowledge/computed/co-change-clusters.md new file mode 100644 index 00000000..4f05d7a8 --- /dev/null +++ b/.claude/knowledge/computed/co-change-clusters.md @@ -0,0 +1,8 @@ +# Co-Change Clusters +# Auto-generated — do not edit manually + +## Files That Frequently Change Together (last 90 days) + + +--- +_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_ diff --git a/.claude/knowledge/computed/contributor-summary.md b/.claude/knowledge/computed/contributor-summary.md new file mode 100644 index 00000000..17446b64 --- /dev/null +++ b/.claude/knowledge/computed/contributor-summary.md @@ -0,0 +1,10 @@ +# Contributor Summary +# Auto-generated — do not edit manually + +## Top Contributors (last 90 days) + +| Author | Commits | Files Touched | +|--------|---------|--------------| + +--- +_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_ diff --git a/.claude/knowledge/computed/git-intelligence.json b/.claude/knowledge/computed/git-intelligence.json new file mode 100644 index 00000000..5de12992 --- /dev/null +++ b/.claude/knowledge/computed/git-intelligence.json @@ -0,0 +1,24 @@ +{ + "generated": "2026-03-23T00:00:00Z", + "period_days": 180, + "since": "2025-09-24", + "recent_activity": { + "total_commits": 1, + "unique_authors": 1, + "fix_bug_commits": 0, + "first_commit_date": "2026-03-20", + "last_commit_date": "2026-03-20", + "commits_per_month": { + "2026-03": 1 + } + }, + "top_authors": [ + {"author": "Mian Leow", "commits": 1} + ], + "file_churn": [ + {"file": "CLAUDE.md", "commits": 1}, {"file": ".claude/review-rules.yml", "commits": 1}, {"file": ".claude/knowledge/adrs/003-create-aar-task.md", "commits": 1}, {"file": ".claude/knowledge/adrs/002-listener-separation.md", "commits": 1}, {"file": ".claude/knowledge/adrs/001-abstract-adapter-pattern.md", "commits": 1}, {"file": ".claude/conventions.yml", "commits": 1} + ], + "bug_hotspots": [ + + ] +} diff --git a/.claude/knowledge/computed/hotspot-map.md b/.claude/knowledge/computed/hotspot-map.md new file mode 100644 index 00000000..b57fca7b --- /dev/null +++ b/.claude/knowledge/computed/hotspot-map.md @@ -0,0 +1,17 @@ +# Git Hotspot Map +# Auto-generated — do not edit manually +# Re-run: .claude/scripts/git-hotspots.sh + +## Most Frequently Changed Files (last 90 days) + +| Rank | File | Commits | Last Changed | +|------|------|---------|-------------| +| - | `CLAUDE.md` | 1 | 2026-03-20 | +| - | `.claude/review-rules.yml` | 1 | 2026-03-20 | +| - | `.claude/knowledge/adrs/003-create-aar-task.md` | 1 | 2026-03-20 | +| - | `.claude/knowledge/adrs/002-listener-separation.md` | 1 | 2026-03-20 | +| - | `.claude/knowledge/adrs/001-abstract-adapter-pattern.md` | 1 | 2026-03-20 | +| - | `.claude/conventions.yml` | 1 | 2026-03-20 | + +--- +_Generated: 2026-03-24 | Period: 2025-12-24 to 2026-03-24 | Commits: 1 | Authors: 0_ diff --git a/.claude/knowledge/computed/meta.yml b/.claude/knowledge/computed/meta.yml new file mode 100644 index 00000000..6ecef02c --- /dev/null +++ b/.claude/knowledge/computed/meta.yml @@ -0,0 +1,8 @@ +# Claude knowledge layer metadata +generated: 2026-03-23T00:00:00Z +source_commit: 0363b552ca36e057c49585517300addd82af1255 +stale_if_older_than: 7d +artifacts: + git-intelligence.json: { bytes: 826, tokens: ~206 } +total_bytes: 826 +total_tokens: ~206 diff --git a/.claude/review-rules.yml b/.claude/review-rules.yml new file mode 100644 index 00000000..36569f4c --- /dev/null +++ b/.claude/review-rules.yml @@ -0,0 +1,29 @@ +review_rules: + - path: "Adapters/**/*.{java,kt}" + rules: + - Adapter must extend AbstractAdapter and implement INetworkInitCallbackListener + - All three ad formats (Rewarded Video, Interstitial, Banner) must be supported + - Load and play listeners must be separate classes per format + - SDK initialization must use the callback listener pattern + - All ad callbacks must be invoked on the main/UI thread + - Null safety checks required before invoking listener methods + + - path: "**/build.gradle" + rules: + - minSdkVersion must be 16 or higher as required by the network SDK + - targetSdkVersion should be 30 or current project standard + - mediationsdk dependency version must be compatible with 7.2.4.1 + - createAAR task must be present and correctly configured + - No snapshot dependencies in release builds + + - path: "Adapters/**/*Listener*.{java,kt}" + rules: + - Listener classes must handle all callback methods for their format + - Load listeners handle ad load success/failure + - Play listeners handle ad show, click, close, and reward events + - Must not hold strong references that could cause memory leaks + + - path: "ReleaseCandidates/**" + rules: + - Only AAR artifacts should be committed to this directory + - AAR filenames must include adapter name and version diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..5218fa44 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,40 @@ +# LevelPlay Android Adapters + +## Overview +Unity LevelPlay (ironSource) Android mediation adapters. Contains 10 network adapters for serving ads through the ironSource mediation platform. + +## Languages +- **Java** (primary, 36 files) — most adapters +- **Kotlin** (8 files) — Pangle and Yahoo adapters only + +## Build System +- **Gradle** with Android Gradle Plugin 4.2.0 +- Android Library modules +- Custom `createAAR` Gradle task for building release artifacts +- Build output: `./gradlew createAAR` produces AARs in `ReleaseCandidates/` directory + +## Architecture +- Directory structure: `Adapters/{Network}/IS{Network}Adapter/` +- All adapters extend `AbstractAdapter` base class +- Implement `INetworkInitCallbackListener` for SDK initialization +- Separate listener classes per ad format (load listener + play listener) + +## Ad Formats +- Rewarded Video (all adapters) +- Interstitial (all adapters) +- Banner (all adapters) + +## Vungle Adapter +- Adapter version: v4.3.36 +- VungleAds SDK: 7.4.0 + +## Platform Requirements +- Target SDK: 30 +- Min SDK: 16 +- IronSource SDK (mediationsdk): 7.2.4.1 + +## Key Conventions +- Adapter naming: `IS{Network}Adapter` +- Listener separation: distinct load and play listener classes per format +- AbstractAdapter provides shared initialization and lifecycle +- AAR artifacts built via custom Gradle task