Skip to content

Collecting Device Data ​

Where each device_data field comes from on Android, and what attribution loses without it.

device_data is the object you send on init, attribution-data, and signup. It carries the signals Linkrunner uses to match an install to a click. Most fields are simple lookups. Three need Google Play libraries and do the heavy lifting for attribution.

The full client collects all of these for you. This page explains each one so you can trust, trim, or rebuild that collection.

Dependencies ​

gradle
dependencies {
    // Advertising ID (GAID)
    implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
    // App Set ID
    implementation 'com.google.android.gms:play-services-appset:16.0.2'
    // Google Play Install Referrer
    implementation 'com.android.installreferrer:installreferrer:2.2'
}

Going SDK-less does not remove these libraries. They are how Android exposes the advertising ID and the install referrer, so you need them no matter how you call Linkrunner.

Permissions and queries ​

xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />

<!-- Lets you see the Meta apps for the Meta install referrer (Android 11+) -->
<queries>
    <package android:name="com.facebook.katana" />
    <package android:name="com.instagram.android" />
    <package android:name="com.facebook.lite" />
</queries>

What to send ​

FieldSource on AndroidImportance
gaidAdvertisingIdClientHigh. Google Ads and device matching.
install_ref (and related)Play Install ReferrerHigh. Carries gclid, fbclid, utm_*, Meta referrer.
meta_install_refMeta content providerHigh for Meta ads.
device_idSettings.Secure.ANDROID_IDRecommended. Matching fallback.
device_ipNetwork interfacesRecommended. IP matching fallback.
user_agentWebSettings.getDefaultUserAgentRecommended. Fingerprint matching.
app_version, build_number, bundle_id, application_namePackageManagerRecommended.
manufacturer, brand, device_name, system_versionBuildRecommended.
connectivity, carrierSystem servicesOptional.
appsetid, appsetid_scopeAppSetOptional.

If you send only the basics and skip gaid, install_ref, and meta_install_ref, install registration still works, but paid attribution for Google and Meta will be weak or missing.

Advertising ID (GAID) ​

The Google Advertising ID is the primary signal for Google Ads and device matching. Read it off the main thread.

kotlin
fun gaid(context: Context): String? = try {
    val info = AdvertisingIdClient.getAdvertisingIdInfo(context)
    if (info.isLimitAdTrackingEnabled) null else info.id
} catch (e: Exception) { null }

It returns null when the user has limited ad tracking. If your app targets children, do not collect the GAID and remove the AD_ID permission. See the SDK guidance on AAID.

Google Play Install Referrer ​

The install referrer is the single most valuable attribution signal on Android. It carries the gclid, fbclid, utm_* parameters, and the Meta encrypted referrer from the Play Store click that led to the install.

kotlin
suspend fun installReferrer(context: Context): JSONObject? = withContext(Dispatchers.IO) {
    suspendCancellableCoroutine { cont ->
        val client = InstallReferrerClient.newBuilder(context).build()
        client.startConnection(object : InstallReferrerStateListener {
            override fun onInstallReferrerSetupFinished(code: Int) {
                try {
                    if (code == InstallReferrerClient.InstallReferrerResponse.OK) {
                        val r = client.installReferrer
                        cont.resume(JSONObject().apply {
                            put("install_ref", r.installReferrer ?: "")
                            put("install_ref_install_version", r.installVersion ?: "")
                            put("install_ref_installBeginTimestampSeconds", r.installBeginTimestampSeconds)
                            put("install_ref_referrerClickTimestampSeconds", r.referrerClickTimestampSeconds)
                            put("install_ref_googlePlayInstantParam", r.googlePlayInstantParam)
                        })
                    } else cont.resume(null)
                } catch (e: Exception) {
                    cont.resume(null)
                } finally {
                    runCatching { client.endConnection() }
                }
            }
            override fun onInstallReferrerServiceDisconnected() {
                if (cont.isActive) cont.resume(null)
            }
        })
        cont.invokeOnCancellation { runCatching { client.endConnection() } }
    }
}

The install referrer is available right after install. Read it once on first launch, send it with init, and cache it. The Play Store keeps it only briefly, so do not delay the first read.

Meta Install Referrer ​

For Meta (Facebook and Instagram) attribution, query the Meta install referrer from the installed Meta app. It needs your Facebook App ID in the manifest and the <queries> block above.

xml
<application>
    <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb1234567890" />
    <!-- If you do not use the Facebook SDK, use this key instead: -->
    <!-- <meta-data android:name="com.linkrunner.FacebookApplicationId" android:value="1234567890" /> -->
</application>
kotlin
fun metaInstallReferrer(context: Context): JSONObject? {
    val appId = facebookAppId(context) ?: return null
    val providers = listOf(
        "com.facebook.katana.provider.InstallReferrerProvider" to "facebook",
        "com.instagram.contentprovider.InstallReferrerProvider" to "instagram",
        "com.facebook.lite.provider.InstallReferrerProvider" to "facebook_lite",
    )
    for ((authority, source) in providers) {
        if (context.packageManager.resolveContentProvider(authority, 0) == null) continue
        val uri = Uri.parse("content://$authority/$appId")
        context.contentResolver.query(
            uri, arrayOf("install_referrer", "is_ct", "actual_timestamp"), null, null, null
        )?.use { c ->
            if (c.moveToFirst()) {
                val refIdx = c.getColumnIndex("install_referrer")
                val ref = if (refIdx >= 0) c.getString(refIdx) else null
                if (!ref.isNullOrEmpty()) return JSONObject().apply {
                    put("install_referrer", ref)
                    put("source", source)
                    c.getColumnIndex("is_ct").let { if (it >= 0) put("is_ct", c.getInt(it)) }
                    c.getColumnIndex("actual_timestamp").let { if (it >= 0) put("actual_timestamp", c.getLong(it)) }
                }
            }
        }
    }
    return null
}

fun facebookAppId(context: Context): String? = try {
    val meta = context.packageManager
        .getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).metaData
    meta?.getString("com.facebook.sdk.ApplicationId")
        ?: meta?.getString("com.linkrunner.FacebookApplicationId")
} catch (e: Exception) { null }

See Meta Install Referrer for how Linkrunner uses this signal.

Everything else ​

These are plain system lookups. The full client includes them.

  • device_id: Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
  • device_ip: first non-loopback IPv4 from NetworkInterface.getNetworkInterfaces()
  • user_agent: WebSettings.getDefaultUserAgent(context)
  • manufacturer, brand, device_name, system_version: Build.MANUFACTURER, Build.BRAND, Build.MODEL, Build.VERSION.RELEASE
  • App info: PackageManager.getPackageInfo for app_version and build_number, context.packageName for bundle_id

Need help? Contact support@linkrunner.io

Need help? Contact support@linkrunner.io