// Add implementation("com.android.installreferrer:installreferrer:2.2") to your app module. // No SAIRO secret belongs in the app. Forward pending values to YOUR authenticated backend. package com.sairo.referral import android.content.Context import android.net.Uri import com.android.installreferrer.api.InstallReferrerClient import com.android.installreferrer.api.InstallReferrerStateListener class SairoReferral(context: Context) { private val context = context.applicationContext private val prefs = this.context.getSharedPreferences("sairo_referral", Context.MODE_PRIVATE) fun pending(): String? = prefs.getString("pending", null) fun capture(url: Uri): Boolean { val ref = url.getQueryParameter("ref") ?: url.getQueryParameter("sairo_ref") val code = url.getQueryParameter("sairo_code") val value = ref?.takeIf { it.length <= 2000 && Regex("[A-Za-z0-9_-]+\\.[a-f0-9]{64}").matches(it) } ?: code?.replace("-", "")?.trim()?.uppercase()?.takeIf { Regex("[A-F0-9]{20}").matches(it) } ?: return false // Keep first pending referral. Your backend must not overwrite committed attribution. if (pending() == null) prefs.edit().putString("pending", value).apply() return true } fun enterCode(code: String): Boolean = capture(Uri.Builder().scheme("https").authority("local.invalid").appendQueryParameter("sairo_code", code).build()) // Clear only after your backend commits attribution, not before a network request. fun acknowledge() { prefs.edit().remove("pending").apply() } // Invoke once at first launch; transient failures can be retried on a later launch. fun readInstallReferrer(done: (Boolean) -> Unit) { if (prefs.getBoolean("install_read", false)) { done(pending() != null); return } val client = InstallReferrerClient.newBuilder(context).build() var finished = false fun finish(ok: Boolean) { if (!finished) { finished = true; client.endConnection(); done(ok) } } try { client.startConnection(object : InstallReferrerStateListener { override fun onInstallReferrerSetupFinished(responseCode: Int) { if (responseCode != InstallReferrerClient.InstallReferrerResponse.OK) { finish(false); return } try { val value = client.installReferrer.installReferrer val captured = capture(Uri.parse("https://local.invalid/?" + value)) prefs.edit().putBoolean("install_read", true).apply() finish(captured) } catch (_: Exception) { finish(false) } } override fun onInstallReferrerServiceDisconnected() { finish(false) } }) } catch (_: Exception) { finish(false) } } }