diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28c1dae..f854100 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,6 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9afc8b5..80a6eee 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,9 +30,6 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b617a..983b677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +Open Measurement moves to its own artifact, as kontextkit-ios has `KontextKitOM`. + +### Breaking +* `OmManager`, `OmSession`, `OmPartner`, `OmCreativeType` and the bundled `omsdk_v1.js` are in the new `:om` module, published as `so.kontext.kit:kontext-kit-android-om` with the kit's version. Package and class names are unchanged: an SDK that measures with OMID adds `implementation("so.kontext.kit:kontext-kit-android-om:")` next to the kit and changes nothing else. `kontext-kit-android` itself no longer carries the IAB OMSDK AAR, so an SDK without OMID (the video SDK) ships no OMSDK binary and takes on no OM licence. +* `kontext-kit-android` declares `kotlinx-coroutines-android`, which `AdvertisingIdProvider` already used without declaring it. + ## 0.0.8 Fix an OMID activation crash when `createSession()` is called off the main thread. diff --git a/CLAUDE.md b/CLAUDE.md index f641e19..634f00b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,10 +33,10 @@ Two suffixes carry meaning. Pick whichever fits the code's *shape*, not its doma ``` src/main/kotlin/so/kontext/kit/ deviceinfo/ AdvertisingIdProvider, App/HW/OS/Screen/Battery/Audio/NetworkInfoProvider, BrightnessManager - omsdk/ OmManager, OmSession, OmPartner, OmCreativeType privacy/ TCFDataProvider ui/ InAppBrowserManager -src/main/res/raw/omsdk_v1.js bundled OMID JS for WebView injection +om/ IAB Open Measurement: omsdk/ (OmManager, OmSession, OmPartner, OmCreativeType) and + res/raw/omsdk_v1.js; published as kontext-kit-android-om src/main/AndroidManifest.xml declares ACCESS_NETWORK_STATE + AD_ID perms src/test/kotlin/ mirrors main/ layout (JUnit 4 + Robolectric) local-maven/iab/omsdk-android/ vendored IAB OMID AAR (1.6.4), loaded reflectively at runtime @@ -73,7 +73,7 @@ Versioning: semver. The published version is set via `gradle/libs.versions.toml` - **Kotlin 2.1.0**, JDK 17, AGP 8.7.3, `compileSdk = 34`, `minSdk = 24`. AndroidX-only (Jetifier off). - **`explicitApi()` mode is on** — every top-level and class member must spell out its visibility (`public` / `internal` / `private`) and public functions need an explicit return type. Catches accidentally-public helpers before they become committed surface area. - **No comments** unless the why is non-obvious (hidden constraint, subtle invariant, workaround for a specific bug, behavior that would surprise a reader). Don't ref PRs or tickets in code. -- **OMID AAR is loaded reflectively** by `OmManager` — it isn't declared as a Gradle dep because AAR transform fails in CI unit tests. Consuming apps wire the AAR in their own build. +- **OMID lives in `:om`** (`kontext-kit-android-om`): the kit itself has no OMSDK dependency. `OmManager` loads the OMID classes reflectively; `:om` declares `:omsdk-android` at runtime scope so they reach the host APK. ## Related repos diff --git a/build.gradle.kts b/build.gradle.kts index a7e3c74..bb9cc5b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,21 +53,9 @@ dependencies { // Required by AdvertisingIdProvider (GAID via Play Services) implementation(libs.play.services.ads.identifier) - // IAB OMID Android — `OmManager` accesses OMID purely via reflection, - // but the classes have to be on the runtime classpath of the host app - // for the reflective loader to succeed (otherwise OMID gracefully - // degrades to no-op session lifecycle, killing measurement coverage). - // Declaring as `implementation` here means the IAB coordinate ends up - // in our published POM at `runtime` scope, so AGP merges the AAR into - // the final APK without exposing OMID's API surface on the consumer's - // compile classpath. Same UX as kontextkit-ios shipping the OMSDK - // xcframework inside the podspec. - // - // The `:omsdk-android` subproject republishes the unmodified IAB AAR - // as `so.kontext.iab:omsdk-android:1.6.4` (see its build.gradle.kts). - // Vanniktech maven-publish translates this `project(...)` dep to the - // Maven coordinate in the published POM. - implementation(project(":omsdk-android")) + // AdvertisingIdProvider's suspend read runs on Dispatchers.IO. + implementation(libs.kotlinx.coroutines.android) + // Open Measurement lives in `:om` (`kontext-kit-android-om`): the kit itself carries no OMSDK binary. // Tests use JUnit 4 + Robolectric (matches v3 sdk-kotlin's :ads test stack). testImplementation(libs.junit) @@ -98,7 +86,7 @@ mavenPublishing { pom { name.set("Kontext Kit Android") - description.set("Shared Android primitives (device info, privacy, in-app browser, OMID) for the Kontext Kotlin / React Native / Flutter SDKs.") + description.set("Shared Android primitives (device info, privacy, in-app browser) for the Kontext Kotlin / React Native / Flutter SDKs.") url.set("https://www.kontext.so/advertisers") licenses { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 115c1da..f4e0565 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,7 @@ play-services-ads-identifier = { module = "com.google.android.gms:play-services- junit = { module = "junit:junit", version.ref = "junit" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } diff --git a/om/build.gradle.kts b/om/build.gradle.kts new file mode 100644 index 0000000..c86d71f --- /dev/null +++ b/om/build.gradle.kts @@ -0,0 +1,136 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.maven.publish) + alias(libs.plugins.spotless) + alias(libs.plugins.detekt) +} + +// Maven coordinates published to Maven Central as +// `so.kontext.kit:kontext-kit-android-om:`: IAB Open Measurement +// on top of the kit, released with it under the same version. Only the SDKs +// that measure with OMID depend on it. +// +// The published version is `libs.versions.kit` (gradle/libs.versions.toml) +// unless overridden by `-PkitVersion=X.Y.Z` at publish time (CI does this +// from the git tag — see .github/workflows/publish.yml). +val kitVersion = providers.gradleProperty("kitVersion").orElse(libs.versions.kit.get()) +group = "so.kontext.kit" +version = kitVersion.get() + +android { + namespace = "so.kontext.kit.om" + compileSdk = 34 + defaultConfig { + minSdk = 24 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + + testOptions { + unitTests.isIncludeAndroidResources = true + unitTests.isReturnDefaultValues = true + } +} + +// Strict API mode — every top-level / class member must spell out its +// visibility (`public` / `internal` / `private`) and public functions need +// an explicit return type. Catches accidentally-public helpers before they +// become committed surface area on the published artifact. +kotlin { + explicitApi() +} + +dependencies { + // The kit's types (Context helpers) are part of this module's API. + api(project(":")) + // OmManager.createSession is suspend and runs its WebView work on the main dispatcher. + implementation(libs.kotlinx.coroutines.android) + // IAB OMID Android. `OmManager` accesses OMID purely via reflection, + // but the classes have to be on the runtime classpath of the host app + // for the reflective loader to succeed (otherwise OMID degrades to a + // no-op session lifecycle). As `implementation`, the IAB coordinate is + // in the published POM at `runtime` scope: AGP merges the AAR into the + // APK without exposing OMID's API on the consumer's compile classpath. + // `:omsdk-android` republishes the unmodified IAB AAR as + // `so.kontext.iab:omsdk-android:1.6.4` (see its build.gradle.kts). + implementation(project(":omsdk-android")) + + testImplementation(libs.junit) + testImplementation(libs.robolectric) + testImplementation(libs.androidx.test.core) + testImplementation(libs.kotlinx.coroutines.test) +} + +// Vanniktech maven-publish — same pattern as v3 sdk-kotlin (`:ads`). +// Activates publishing to Maven Central with auto-release + signing. +// Credentials come from Gradle properties at publish time. +mavenPublishing { + publishToMavenCentral(automaticRelease = true) + // Local `publishToMavenLocal` runs without GPG creds, so don't enable + // signing then. Maven Central enforces signatures on upload — CI sets + // `signingInMemoryKey` env which flips this branch on. + val hasSigningKey = (findProperty("signingInMemoryKey") as String?) != null || + System.getenv("ORG_GRADLE_PROJECT_signingInMemoryKey") != null + if (hasSigningKey) { + signAllPublications() + } + + coordinates( + groupId = group.toString(), + artifactId = "kontext-kit-android-om", + version = version.toString(), + ) + + pom { + name.set("Kontext Kit Android: Open Measurement") + description.set("IAB Open Measurement (OMID) for the Kontext Android SDKs, on top of Kontext Kit Android.") + url.set("https://www.kontext.so/advertisers") + + licenses { + license { + name.set("Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + distribution.set("repo") + } + } + + developers { + developer { + id.set("kontext") + name.set("kontext") + } + } + + scm { + url.set("https://github.com/kontextso/kontextkit-android") + connection.set("scm:git:https://github.com/kontextso/kontextkit-android.git") + developerConnection.set("scm:git:ssh://github.com/kontextso/kontextkit-android.git") + } + } +} + +// Lint / style — same versions as sdk-kotlin so contributors don't have +// two ktlint/detekt baselines to chase. +spotless { + kotlin { + target("src/**/*.kt") + targetExclude("**/build/**/*.kt") + ktlint(libs.versions.ktlint.get()) + } + kotlinGradle { + target("*.gradle.kts") + targetExclude("**/build/**/*.kts") + ktlint(libs.versions.ktlint.get()) + } +} + +detekt { + buildUponDefaultConfig = true + parallel = true +} diff --git a/om/src/main/AndroidManifest.xml b/om/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cc947c5 --- /dev/null +++ b/om/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/src/main/kotlin/so/kontext/kit/omsdk/OmCreativeType.kt b/om/src/main/kotlin/so/kontext/kit/omsdk/OmCreativeType.kt similarity index 100% rename from src/main/kotlin/so/kontext/kit/omsdk/OmCreativeType.kt rename to om/src/main/kotlin/so/kontext/kit/omsdk/OmCreativeType.kt diff --git a/src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt b/om/src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt similarity index 99% rename from src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt rename to om/src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt index 1076214..c1c9339 100644 --- a/src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt +++ b/om/src/main/kotlin/so/kontext/kit/omsdk/OmManager.kt @@ -6,7 +6,7 @@ import android.os.Looper import android.webkit.WebView import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine -import so.kontext.kit.R +import so.kontext.kit.om.R import kotlin.coroutines.resume /** diff --git a/src/main/kotlin/so/kontext/kit/omsdk/OmPartner.kt b/om/src/main/kotlin/so/kontext/kit/omsdk/OmPartner.kt similarity index 100% rename from src/main/kotlin/so/kontext/kit/omsdk/OmPartner.kt rename to om/src/main/kotlin/so/kontext/kit/omsdk/OmPartner.kt diff --git a/src/main/kotlin/so/kontext/kit/omsdk/OmSession.kt b/om/src/main/kotlin/so/kontext/kit/omsdk/OmSession.kt similarity index 100% rename from src/main/kotlin/so/kontext/kit/omsdk/OmSession.kt rename to om/src/main/kotlin/so/kontext/kit/omsdk/OmSession.kt diff --git a/src/main/res/raw/omsdk_v1.js b/om/src/main/res/raw/omsdk_v1.js similarity index 100% rename from src/main/res/raw/omsdk_v1.js rename to om/src/main/res/raw/omsdk_v1.js diff --git a/src/test/kotlin/so/kontext/kit/omsdk/OmCreativeTypeTest.kt b/om/src/test/kotlin/so/kontext/kit/omsdk/OmCreativeTypeTest.kt similarity index 100% rename from src/test/kotlin/so/kontext/kit/omsdk/OmCreativeTypeTest.kt rename to om/src/test/kotlin/so/kontext/kit/omsdk/OmCreativeTypeTest.kt diff --git a/src/test/kotlin/so/kontext/kit/omsdk/OmManagerTest.kt b/om/src/test/kotlin/so/kontext/kit/omsdk/OmManagerTest.kt similarity index 100% rename from src/test/kotlin/so/kontext/kit/omsdk/OmManagerTest.kt rename to om/src/test/kotlin/so/kontext/kit/omsdk/OmManagerTest.kt diff --git a/src/test/kotlin/so/kontext/kit/omsdk/OmPartnerTest.kt b/om/src/test/kotlin/so/kontext/kit/omsdk/OmPartnerTest.kt similarity index 100% rename from src/test/kotlin/so/kontext/kit/omsdk/OmPartnerTest.kt rename to om/src/test/kotlin/so/kontext/kit/omsdk/OmPartnerTest.kt diff --git a/src/test/kotlin/so/kontext/kit/omsdk/OmSessionTest.kt b/om/src/test/kotlin/so/kontext/kit/omsdk/OmSessionTest.kt similarity index 100% rename from src/test/kotlin/so/kontext/kit/omsdk/OmSessionTest.kt rename to om/src/test/kotlin/so/kontext/kit/omsdk/OmSessionTest.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 14759d9..da225c7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -65,3 +65,6 @@ rootProject.name = "kontext-kit-android" // Maven Central without vendoring the AAR themselves. See // `omsdk-android/build.gradle.kts` for the full rationale. include(":omsdk-android") +// IAB Open Measurement on top of the kit, as kontextkit-ios has KontextKitOM: only the SDKs that measure with OMID +// depend on it, the others never ship the OMSDK binary or take on its licence. +include(":om")