Android SDK
The Active Reach Android SDK tracks user behavior, identifies users, and manages FCM push tokens on Android 5.0+ (API 21+).
Current version: ai.active-reach:android-sdk:1.6.0 (Maven Central).
Brand canon. The Maven coordinate is
ai.active-reach:android-sdk, but the Kotlin import path you actually write in your code isai.aegis.sdk.*. This is intentional: “Aegis” is the internal source-tree name; “Active Reach” is the customer-facing identifier on the registry. The classAegisand its companionAegisConfigkeep the internal name; only the Gradle coordinate switched.
Installation
Add the dependency to your app-level build.gradle.kts:
dependencies {
implementation("ai.active-reach:android-sdk:1.6.0")
}Initialization
Initialize in your Application class:
import ai.aegis.sdk.Aegis
import ai.aegis.sdk.AegisConfig
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Aegis.init(
context = this,
writeKey = "YOUR_WRITE_KEY",
config = AegisConfig(workspaceId = "YOUR_WORKSPACE_ID")
)
}
}Core tracking
// Track a custom event
Aegis.track("order_completed", mapOf(
"order_id" to "ORD-123",
"amount" to 4999,
"currency" to "INR"
))
// Identify a user
Aegis.identify("user_456", mapOf(
"name" to "Priya Sharma",
"email" to "[email protected]",
"plan" to "pro"
))
// Track a screen view
Aegis.screen("Product Detail", mapOf(
"product_id" to "SKU-A"
))
// Group a user into a company
Aegis.group("company_789", mapOf("name" to "Acme Corp"))
// Merge anonymous + identified sessions
Aegis.alias("user_456")
// Clear identity on logout
Aegis.reset()
// Flush before app termination
Aegis.flush()Event governance
The Android SDK ships with client-side event-name cap enforcement — byte-identical hashing with the web SDK, Python control-plane, and iOS SDK. When you bootstrap the SDK, the server returns a compact Bloom filter of your registered event names + a counter of remaining headroom. The SDK uses this to drop novel names locally once you hit your plan cap.
Integration
import ai.aegis.sdk.Aegis
import ai.aegis.sdk.governance.EventGovernanceHint
// 1. Bootstrap against control-plane (typically in your app onboarding).
val bootstrap: BootstrapResult = bootstrap(
apiHost = "https://api.active-reach.ai",
writeKey = BuildConfig.AEGIS_WRITE_KEY,
)
// 2. Initialize the SDK.
Aegis.initialize(application, BuildConfig.AEGIS_WRITE_KEY, AegisConfig(
apiHost = "https://api.active-reach.ai",
))
// 3. Feed the hint to the governor.
Aegis.ingestGovernanceHint(bootstrap.eventGovernance)Pass null to disable (fail-open — gateway remains authoritative):
Aegis.ingestGovernanceHint(null)What the SDK does
Identical semantics to the web + iOS SDKs — bloom-check for known names, per-instance memo of novel names so repeating a name doesn’t double-charge, drop-and-coalesce for novel names past the cap, fail-open during the server’s 7-day grace window.
Drops surface in two ways:
- A coalesced
aegis.client.name_governor_droppedmeta event rides the next batch flush — ops dashboards see the pattern - A single
Log.w("AegisGovernor", "…")on the first dropped name per session — developers see the issue in logcat immediately
The EventGovernanceHint type
@Serializable
data class EventGovernanceHint(
@SerialName("bloom_algo") val bloomAlgo: String, // always "mmh3_x86_32_km"
@SerialName("seed_a") val seedA: Int, // always 0
@SerialName("seed_b") val seedB: Int, // always 1
val k: Int, // typically 7
val m: Int, // power-of-2 bitarray size
@SerialName("bloom_b64") val bloomB64: String, // base64 of the bitarray
@SerialName("remaining_new_names") val remainingNewNames: Int? = null,
@SerialName("grace_active") val graceActive: Boolean = false,
@SerialName("ttl_seconds") val ttlSeconds: Int = 300,
)Decode directly from the /v1/sdk/bootstrap JSON via kotlinx.serialization:
val json = Json { ignoreUnknownKeys = true }
val hint = json.decodeFromString<EventGovernanceHint>(jsonString)Thread-safety
NameGovernor uses synchronized(lock) on all mutating paths (ingestHint, shouldSend, drainDropReport) — safe to call from any thread concurrently, including UI thread + background batch flush.
Push notifications
Register the FCM token:
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
Aegis.push.registerDeviceToken(token)
}Track delivery and clicks:
Aegis.push.trackDelivery(messageId = "msg_123")
Aegis.push.trackClick(messageId = "msg_123")Requires FCM setup — see Push setup guide.
Push engagement reporting (SDK ≥1.4.0)
The Android SDK ships with AegisPushTracker which fires push
lifecycle events to the canonical ingestion endpoint without
host-app code.
| Lifecycle | Hook | What it posts |
|---|---|---|
| Delivered | AegisFirebaseMessagingService.onMessageReceived | event_type: "push.delivered" |
| Clicked | AegisPushClickActivity (registered by the SDK manifest) | event_type: "push.clicked" with metadata.action_url |
| Dismissed | AegisPushDismissReceiver (wired via setDeleteIntent on NotificationCompat.Builder) | event_type: "push.dismissed" |
| Subscribed | Aegis.push.registerDeviceToken(token) | event_type: "push.subscribed" |
To enable dismiss tracking on rich notifications you build yourself,
set the SDK’s DeleteIntent on your builder:
val deleteIntent = AegisPushDismissReceiver.deleteIntent(context, messageId, campaignId)
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(body)
.setDeleteIntent(deleteIntent)
.build()Wire shape for all four signals: POST /v1/push/engagement — see
Event ingestion.
Advanced features including deep linking, rich notification attachments, and background tracking are documented in the full Android SDK reference. Coming soon.
E-commerce helpers (Phase 1, SDK ≥1.1.0)
Use Aegis.ecommerce for the canonical 19-method tracker — same wire shape as the web SDK’s EcommerceTracker, so the cell-plane canonical_mapper consumes events identically across web + mobile.
import ai.aegis.sdk.ecommerce.*
Aegis.ecommerce.productViewed(EcommerceProduct(
productId = "sku-42",
name = "Cotton Tee",
price = 1299.0,
currency = "INR",
))
Aegis.ecommerce.addToCart(product)
Aegis.ecommerce.orderCompleted(EcommerceOrder(
orderId = "ord-100",
value = 1299.0,
products = listOf(product),
))
// Back-in-stock waitlist — arms the catalog.back_in_stock journey trigger.
Aegis.ecommerce.productWaitlisted(EcommerceWaitlist(
product = product,
channels = listOf(WaitlistChannel.WHATSAPP),
))Cart abandonment is detected server-side — the SDK only emits the canonical cart events.
Trait governance (Phase 1, SDK ≥1.1.0)
identify() and group() run their traits through a client-side TraitGovernor that mirrors the 5 ingestion guards the cell-plane backend enforces. Drops surface as Log.w(AegisTraitGovernor, …) lines — rate-limited to first 3 per (workspace, verdict) per session.
Behaviour:
- camelCase keys auto-rewritten to snake_case
- Reserved prefixes dropped (
system.,user.,loyalty.,cart.,bill.,$,_, …) - Long strings truncated; oversized strings rejected (DoS protection)
- ISO-8601 / epoch values on date-keyed fields parsed to epoch-ms
The four-surface drift contract (Kotlin verdict → backend record_attribute_keys verdict → DB ingestion_dlq.verdict enum → frontend IngestionDLQVerdict union) is pinned by libs/mobile-sdk/tests/drift/trait-governor-verdicts.json. See Event governance → TraitGovernor for the full verdict matrix.
Multi-region cell selection (Phase 1, SDK ≥1.1.0)
AegisConfig now accepts cellEndpoints + preferredRegion + autoRegionDetection + workspaceId. When cellEndpoints is empty the SDK falls back to apiHost.
val config = AegisConfig(
cellEndpoints = listOf(
CellEndpoint(CellRegion.AP_SOUTH, "https://ap-south.api.aegis.ai", priority = 10),
CellEndpoint(CellRegion.EU_CENTRAL, "https://eu-central.api.aegis.ai", priority = 20),
),
preferredRegion = CellRegion.AP_SOUTH,
autoRegionDetection = false,
workspaceId = "ws_abc123",
)/v1/sdk/bootstrap handshake (Phase 1, SDK ≥1.1.0)
Aegis.bootstrap() performs the canonical handshake against your control-plane. On success the EventGovernanceHint is auto-applied to the NameGovernor.
val result = Aegis.bootstrap() // throws BootstrapError on 401/403
// result.propertyId, result.workspaceId, result.locationCodesWhat’s next
- Push primer (soft-ask) — Android 13+ POST_NOTIFICATIONS soft-ask
- Geofencing & location — Play Services geofencing (opt-in)
- Push notification setup
- In-app messaging setup
- Example app — a runnable sample wiring tracking, identify, and lifecycle