Android Integration
This guide covers the integration of Device Intel SDK into Android applications.
Overview
Device Intel is a lightweight library for Android device identification and risk detection. The SDK provides:
- Persistent Device ID — Unique identifier that persists across app reinstalls
- Risk Detection — Detection of emulators, rooted devices, VPNs, and other security signals
- Minimal Footprint — No required permissions for core functionality
Prerequisites
| Requirement | Version |
|---|---|
| Minimum SDK | Android 7.0 (API 24) |
| Target SDK | Android 14+ (API 34+) |
| Java | 17+ |
Installation
Add the dependency to your app's build.gradle.kts:
dependencies {
implementation("ai.mubeen:deviceintel-lib:1.7.3")
}
1.7.3 was the latest release at the time of writing. Check Maven Central for the most recent version before integrating.
Quick Start
1. Import the SDK
- Kotlin
- Java
import ai.mubeen.deviceintel.MB_DeviceConfig
import ai.mubeen.deviceintel.MB_DeviceIntel
import ai.mubeen.deviceintel.ErrorCallback
import ai.mubeen.deviceintel.DeviceIntelError
import org.json.JSONObject
import ai.mubeen.deviceintel.MB_DeviceConfig;
import ai.mubeen.deviceintel.MB_DeviceIntel;
import ai.mubeen.deviceintel.ErrorCallback;
import ai.mubeen.deviceintel.DeviceIntelError;
import org.json.JSONObject;
2. Initialize the SDK
Initialize the SDK once during application startup, typically in your Application class.
- Kotlin
- Java
The example below sets every option explicitly. In Kotlin you can omit any field to fall back to its default and use named arguments, so a minimal setup can be as short as MB_DeviceConfig(apiKey = "public_your_key").
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val config = MB_DeviceConfig(
apiKey = "public_your_key",
uploadOnGetId = true,
debugMode = false,
collectionTimeoutMs = 5000L,
retryCount = 3,
baseUrl = "https://fp.sa.mubeen.ai/",
compressEnabled = true,
errorCallback = object : ErrorCallback {
override fun onError(error: DeviceIntelError) {
Log.e("DeviceIntel", "Error: ${error.type} - ${error.message}")
}
},
collectLocation = false,
locationMaxAgeMs = 1_800_000L,
collectAdvancedSignals = true,
collectAdvertisingId = false
)
MB_DeviceIntel.init(this, config)
}
}
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
MB_DeviceConfig config = new MB_DeviceConfig(
"public_your_key", // apiKey
true, // uploadOnGetId
false, // debugMode
5000L, // collectionTimeoutMs
3, // retryCount
"https://fp.sa.mubeen.ai/", // baseUrl
true, // compressEnabled
null, // errorCallback
false, // collectLocation
1800000L, // locationMaxAgeMs
true, // collectAdvancedSignals
false // collectAdvertisingId
);
MB_DeviceIntel.init(this, config);
}
}
MB_DeviceConfig is a Kotlin data class. Its per-parameter defaults are only available to Kotlin callers — from Java you must pass all constructor arguments in order, as shown above. Kotlin callers can use named arguments and omit any field they want to leave at its default.
3. Retrieve the Device ID
- Kotlin
- Java
val deviceId = MB_DeviceIntel.getDeviceId()
Log.d("DeviceIntel", "Device ID: $deviceId")
String deviceId = MB_DeviceIntel.getDeviceId();
Log.d("DeviceIntel", "Device ID: " + deviceId);
When uploadOnGetId is true (the default), each call to getDeviceId() also triggers a full fingerprint collection and background upload. It is not a cheap getter — call it deliberately rather than in hot paths.
When to send a fingerprint
Send a fingerprint with uploadFingerprint() to gather up-to-date data about the device:
- Once when the app is opened — on launch / foreground.
- Once per significant event — such as sign-in, registration, checkout, or any other high-value action you want scored.
- Kotlin
- Java
// On app launch
MB_DeviceIntel.uploadFingerprint()
// Again at each significant event (sign-in, registration, checkout, …)
MB_DeviceIntel.uploadFingerprint()
// On app launch
MB_DeviceIntel.uploadFingerprint();
// Again at each significant event (sign-in, registration, checkout, …)
MB_DeviceIntel.uploadFingerprint();
Manual Fingerprint Upload
Call uploadFingerprint() to collect a fresh fingerprint and upload it on demand, without going through getDeviceId(). This is the recommended trigger when uploadOnGetId = false, or when you want to send a fingerprint for an event without re-reading the device ID.
Collection and upload both run asynchronously on a background thread, so the call returns immediately and never blocks the caller. Failed uploads are queued for retry according to your retryCount configuration.
- Kotlin
- Java
// Collects a fresh fingerprint and uploads it in the background.
MB_DeviceIntel.uploadFingerprint()
// Collects a fresh fingerprint and uploads it in the background.
MB_DeviceIntel.uploadFingerprint();
uploadFingerprint() must be called after MB_DeviceIntel.init(), otherwise it throws IllegalStateException. To inspect the fingerprint locally instead of uploading it, use getFingerprint(), which returns the fingerprint as a JSONObject (synchronous and blocking — call it off the main thread).
Configuration
Basic Configuration
- Kotlin
- Java
val config = MB_DeviceConfig(
apiKey = "public_your_key",
uploadOnGetId = true,
debugMode = false,
collectionTimeoutMs = 5000L,
retryCount = 3,
compressEnabled = true,
errorCallback = object : ErrorCallback {
override fun onError(error: DeviceIntelError) {
Log.e("DeviceIntel", "Error: ${error.type} - ${error.message}")
}
}
)
MB_DeviceIntel.init(this, config)
ErrorCallback errorCallback = error ->
Log.e("DeviceIntel", "Error: " + error.getType() + " - " + error.getMessage());
MB_DeviceConfig config = new MB_DeviceConfig(
"public_your_key", // apiKey
true, // uploadOnGetId
false, // debugMode
5000L, // collectionTimeoutMs
3, // retryCount
"https://fp.sa.mubeen.ai/", // baseUrl
true, // compressEnabled
errorCallback, // errorCallback
false, // collectLocation
1800000L, // locationMaxAgeMs
true, // collectAdvancedSignals
false // collectAdvertisingId
);
MB_DeviceIntel.init(this, config);
ErrorCallback.onError returns void, so the Java lambda must not return a value (error -> Log.e(...), not error -> { ...; return null; }).
Configuration Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
apiKey | String | "" | API key for authentication (e.g. public_...). Required if uploads are enabled. |
uploadOnGetId | Boolean | true | Automatically collect and upload a fingerprint when getDeviceId() is called. |
debugMode | Boolean | false | Enable verbose logcat logging. Enable only for debugging. |
collectionTimeoutMs | Long | 5000 | Timeout for data collection, in milliseconds. |
retryCount | Int | 3 | Number of retry attempts for failed uploads. Set to 0 to disable retries. |
baseUrl | String | "https://fp.sa.mubeen.ai/" | Base URL of the fingerprint collection API. |
compressEnabled | Boolean | true | Enable gzip compression for network uploads (reduces payload by 30–50%). |
errorCallback | ErrorCallback? | null | Callback for error notifications. When null, failures are silent. |
collectLocation | Boolean | false | Opt-in. Include the device's last-known cached location. See Optional Data Collection. |
locationMaxAgeMs | Long | 1800000 | Max acceptable age (ms) of a cached location fix; older fixes report as null. 30 minutes by default. |
collectAdvancedSignals | Boolean | false | Opt-in. Include permission-gated WiFi SSID/BSSID, Bluetooth name + paired-device count, phone number, and data-network type. Each field degrades to null unless the host app already holds the matching permission. |
collectAdvertisingId | Boolean | false | Opt-in. Include the Google Advertising ID (GAID). |
Optional Data Collection
The following fields are permission-gated. The SDK never requests runtime permissions itself — each field is read only when the host app already holds the matching permission, and degrades gracefully to null otherwise. Whenever a field is actually collected, the host app is responsible for the runtime permission request, the Google Play Data Safety disclosure, and privacy-policy / consent coverage.
Defaults: collectAdvancedSignals, collectLocation and collectAdvertisingId are all false (opt-in). Set a flag to true to enable that group; a flag left false disables it entirely regardless of granted permissions.
Location (collectLocation)
Reads only a cached last-known fix and never blocks. Requires the host app to already hold ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION. Tune freshness with locationMaxAgeMs.
Advanced signals (collectAdvancedSignals)
Gates these permission-sensitive fields (each degrades to null without the matching permission):
- WiFi SSID / BSSID —
ACCESS_FINE_LOCATION(plusACCESS_WIFI_STATE) and location services enabled. - Bluetooth name + paired-device count —
BLUETOOTH_CONNECTon Android 12+, or install-timeBLUETOOTHbelow 12. - Phone number —
READ_PHONE_NUMBERS/READ_PHONE_STATE(often stillnullon apps targeting API 30+ unless the app is the default SMS/dialer or has carrier privileges). - Data-network type —
READ_PHONE_STATE, orREAD_BASIC_PHONE_STATEon Android 12+.
Carrier / SIM / network metadata and adapter on/off state are collected regardless of this flag; only the identity-revealing fields above are gated by it.
Advertising ID (collectAdvertisingId)
Includes the Google Advertising ID at device_detail.gaid. Read via the official AdvertisingIdClient, so it honors the user's "Limit Ad Tracking" / opt-out setting (returns null when opted out) and degrades to null when Google Play services are unavailable. When enabling it, the host app must:
- Add the
com.google.android.gms:play-services-ads-identifierdependency. - Declare the
com.google.android.gms.permission.AD_IDpermission (required on apps targeting API 33+). - Disclose advertising-ID collection in its Google Play Data Safety form and privacy policy.
Permissions
The SDK declares no permissions in its own manifest and operates without any for core functionality.
Required for network uploads:
<uses-permission android:name="android.permission.INTERNET" />
Optional for enhanced detection:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
The ACCESS_NETWORK_STATE permission enables the connectivity signals (wifiEnabled, network transport, metered state). VPN detection does not require it — it works via network-interface enumeration with no permission. See Optional Data Collection for the additional permissions needed by opt-in signals.
Troubleshooting
Debug Logging
Enable debug mode to capture detailed logs:
MB_DeviceConfig(apiKey = "public_your_key", debugMode = true)
View logs using:
adb logcat | grep "MB_DeviceIntel"
Common Issues
getDeviceId() throws IllegalStateException
MB_DeviceIntel.init() must be called before any other SDK method. Initialize in Application.onCreate(). If storage is temporarily unavailable, getDeviceId() does not throw or return null — it returns a process-stable, non-persistent fallback ID and reports the fault through errorCallback.
Network upload failures
- Verify the
INTERNETpermission is declared in the manifest - Confirm network connectivity on the device
- Validate the API key is correct and active
Risk signals not detected
Certain signals have specific requirements:
- Connectivity signals (
wifiEnabled, network transport, metered state) requireACCESS_NETWORK_STATE adb_enableddetection is limited to debug builds or rooted devices- Opt-in signals require the matching
collect*flag and the corresponding host permission — see Optional Data Collection
Related Documentation
- Server-Side Integration — Backend validation and API reference
- Client-Side Integration — Web SDK integration guide
- Flutter Integration — Flutter plugin that wraps this SDK