Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.0.5

OMID compliance + Kotlin 2.1 baseline.

* `OmSession`: switch HTML display impression owner from `Owner.NATIVE` to `Owner.JAVASCRIPT` (was wrongly assumed to suppress JS geometry polling — IAB validator flagged the resulting impression events). Now matches v3 sdk-kotlin, kontextkit-ios, and the IAB OMID Android v1.6.4 reference demo: `Owner.JAVASCRIPT` for impression on both display and video; `mediaEventsOwner` stays NONE for display, JAVASCRIPT for video. Native-side `AdEvents` construction dropped — JS verification scripts now own `loaded()` + `impressionOccurred()` for both creative types. `OmSession.loaded()` / `impressionOccurred()` kept as no-ops for SDK API compatibility (consuming SDKs still reference them).
* Toolchain: Kotlin 1.9.22 → **2.1.0**, AGP 8.6.1 → 8.7.3, Gradle 8.7 → 8.9, detekt 1.23.4 → 1.23.8, spotless 6.25.0 → 7.2.1. Aligns with sdk-kotlin 2.1 baseline so consuming apps already on Kotlin 2.x stop hitting compiler-output / metadata mismatches.

## 0.0.4

* `InstallIdProvider`: new `deviceinfo/InstallIdProvider.kt`. Returns a UUID v7 per-app-install identifier persisted in a dedicated `kontextso` `SharedPreferences` file under the `installId` key. Generated on first call, survives launches, resets only on uninstall / app-data clear. Mirrors iOS `InstallIdProvider` (UserDefaults-backed) so consumer SDKs can thread the same `installId` field through `/init`, `/preload`, `/error`, and `/debug` request payloads.
Expand Down
10 changes: 5 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
# `./gradlew publish -PkitVersion=1.1.0` so CI can cut releases
# without bumping this file. Consumed by build.gradle.kts as the
# fallback when the `kitVersion` Gradle property is absent.
kit = "0.0.4"
kit = "0.0.5"

# ---- Build tooling ----
agp = "8.6.1" # Android Gradle Plugin
kotlin = "1.9.22" # Kotlin compiler version
agp = "8.7.3" # Android Gradle Plugin
kotlin = "2.1.0" # Kotlin compiler version

# ---- Code-quality plugins ----
maven-publish = "0.34.0" # vanniktech maven-publish
spotless = "6.25.0"
detekt = "1.23.4"
spotless = "7.2.1"
detekt = "1.23.8"
ktlint = "0.50.0" # consumed inside spotless { ktlint(...) }

# ---- AndroidX ----
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Aug 11 16:57:10 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
65 changes: 22 additions & 43 deletions src/main/kotlin/so/kontext/kit/omsdk/OmSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ public class OmSession(
private var session: Any? = null
private var adEvents: Any? = null
private var started = false
private var loadedFired = false
private var impressionFired = false

public val isValid: Boolean get() = session != null

Expand Down Expand Up @@ -68,30 +66,32 @@ public class OmSession(
val context = createContext.invoke(null, partner, webView, url, "")

// Pick creative-type / impression-type / impression-owner / media-events-owner.
// - Display: NATIVE impression owner. SDK fires loaded() + impressionOccurred()
// via AdEvents so the JS verification script does NOT poll geometry and
// does NOT emit a `notFound` geometryChange when the WebView detaches.
// - Video: DEFINED_BY_JAVASCRIPT triple per the IAB OMID Android
// #webview-video docs — JS owns impression + media events.
val nativeOwner = ownerClass.getField("NATIVE").get(null)!!
// Matches v3 sdk-kotlin (`internal/utils/om/WebViewOmSession.kt`) and
// kontextkit-ios (`Sources/OMSDK/OMManager.swift`) — both use
// `Owner.JAVASCRIPT` for the impression owner regardless of creative.
// The JS verification script (use-omid-display-session /
// use-omid-video-session in ads/packages/omsdk) owns firing
// `loaded()` + `impressionOccurred()` from inside the iframe; the
// native side just creates the session.
// - Display: HTML_DISPLAY + BEGIN_TO_RENDER + JS impression + NONE media.
// - Video: DEFINED_BY_JAVASCRIPT triple per the IAB OMID Android
// #webview-video docs.
val jsOwner = ownerClass.getField("JAVASCRIPT").get(null)!!
val noneOwner = ownerClass.getField("NONE").get(null)!!

val omCreativeType: Any
val omImpressionType: Any
val impressionOwner: Any
val mediaOwner: Any
if (creativeType == OmCreativeType.VIDEO) {
omCreativeType = creativeTypeClass.getField("DEFINED_BY_JAVASCRIPT").get(null)!!
omImpressionType = impressionTypeClass.getField("DEFINED_BY_JAVASCRIPT").get(null)!!
impressionOwner = jsOwner
mediaOwner = jsOwner
} else {
omCreativeType = creativeTypeClass.getField("HTML_DISPLAY").get(null)!!
omImpressionType = impressionTypeClass.getField("BEGIN_TO_RENDER").get(null)!!
impressionOwner = nativeOwner
mediaOwner = noneOwner
}
val impressionOwner: Any = jsOwner

// AdSessionConfiguration.createAdSessionConfiguration(
// creative, impression, impressionOwner, mediaOwner, isolateVerificationScripts=false
Expand Down Expand Up @@ -123,15 +123,9 @@ public class OmSession(

session = sess

// For NATIVE impression-owner sessions (display), create AdEvents so the
// SDK can fire loaded() + impressionOccurred() from Kotlin. For JS-owner
// sessions (video) AdEvents is still created but loaded/impression are
// owned by the in-iframe verification script and we don't call them.
if (creativeType != OmCreativeType.VIDEO) {
val adEventsClass = Class.forName("com.iab.omid.library.kontextso.adsession.AdEvents")
val createAdEvents = adEventsClass.getMethod("createAdEvents", sessionClass)
adEvents = createAdEvents.invoke(null, sess)
}
// No AdEvents created — JS verification scripts own `loaded()` +
// `impressionOccurred()` for both display and video now that
// `impressionOwner = Owner.JAVASCRIPT` is used unconditionally.
} catch (e: ReflectiveOperationException) {
android.util.Log.w(TAG, "OM: session init failed", e)
session = null
Expand All @@ -154,36 +148,21 @@ public class OmSession(
}

/**
* Fires the OMID `loaded` event from native code. Only valid for sessions
* with `Owner.NATIVE` as impressionOwner (display). For JS-owner sessions
* (video) the verification script emits this — calling here is a no-op.
* Must be called after [start].
* Kept for SDK API compatibility — no-op. All sessions use
* `Owner.JAVASCRIPT` for the impression owner, so `loaded` is fired by
* the JS verification script inside the iframe (see
* `use-omid-display-session` / `use-omid-video-session` in
* ads/packages/omsdk). The native side does not own this event.
*/
public fun loaded() {
if (loadedFired) return
val ev = adEvents ?: return
try {
ev.javaClass.getMethod("loaded").invoke(ev)
loadedFired = true
} catch (e: ReflectiveOperationException) {
android.util.Log.w(TAG, "OM: AdEvents.loaded failed", e)
}
// intentionally empty — JS owns impressionOwner
}

/**
* Fires the OMID `impressionOccurred` event from native code. Only valid
* for sessions with `Owner.NATIVE` as impressionOwner (display). Must be
* called after [loaded] and after the ad is actually rendered.
* Kept for SDK API compatibility — no-op. See [loaded].
*/
public fun impressionOccurred() {
if (impressionFired) return
val ev = adEvents ?: return
try {
ev.javaClass.getMethod("impressionOccurred").invoke(ev)
impressionFired = true
} catch (e: ReflectiveOperationException) {
android.util.Log.w(TAG, "OM: AdEvents.impressionOccurred failed", e)
}
// intentionally empty — JS owns impressionOwner
}

public fun retire() {
Expand Down
Loading