diff --git a/analytics/build.gradle b/analytics/build.gradle index 781525b3a..4297b3d38 100644 --- a/analytics/build.gradle +++ b/analytics/build.gradle @@ -1,5 +1,7 @@ plugins { id 'com.android.library' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.compose' id 'mixpanel.maven-publish' id 'jacoco' id 'ru.vyarus.animalsniffer' version '2.0.0' @@ -25,6 +27,12 @@ android { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose true + } sourceSets { @@ -146,8 +154,13 @@ dependencies { implementation 'androidx.core:core:1.13.1' implementation 'androidx.lifecycle:lifecycle-process:2.6.2' implementation 'io.github.jamsesso:json-logic-java:1.1.0' + // Curtains: root view (window) observation for autocapture dialog/popup tracking. + // Compatible with other Curtains consumers (e.g., Session Replay SDK). + implementation 'com.squareup.curtains:curtains:1.2.5' // json-logic-java pulls in an outdated json with cve implementation 'com.google.code.gson:gson:2.13.2' + // Compose UI for autocapture semantics extraction (optional - graceful degradation if not present) + compileOnly 'androidx.compose.ui:ui:1.7.7' // AndroidJUnitRunner and JUnit Rules testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:core:1.6.1' @@ -162,6 +175,13 @@ dependencies { testImplementation 'androidx.test:core:1.5.0' testImplementation 'org.json:json:20231013' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + // Compose dependencies for androidTest only (Compose autocapture tests) + androidTestImplementation platform('androidx.compose:compose-bom:2024.12.01') + androidTestImplementation 'androidx.compose.ui:ui' + androidTestImplementation 'androidx.compose.material3:material3' + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + androidTestImplementation 'androidx.activity:activity-compose:1.9.1' + debugImplementation 'androidx.compose.ui:ui-test-manifest:1.7.7' } jacoco { diff --git a/analytics/docs/autocapture.md b/analytics/docs/autocapture.md new file mode 100644 index 000000000..25347f0d4 --- /dev/null +++ b/analytics/docs/autocapture.md @@ -0,0 +1,542 @@ +# Android Autocapture + +Autocapture automatically tracks user interactions in your Android app without requiring manual instrumentation. + +## Overview + +Autocapture captures three types of events: + +| Event | Name | Description | +|-------|------|-------------| +| Click | `$mp_click` | Fired when a user taps any element | +| Rage Click | `$mp_rage_click` | Fired when a user taps rapidly (4+ times) in the same area | +| Dead Click | `$mp_dead_click` | Fired when a tap produces no visible UI response | + +**Privacy:** Autocapture is designed with privacy in mind. No personally identifiable information (PII) is captured by default. + +## Quick Start + +Autocapture is **disabled by default**. Enable it by providing `AutocaptureOptions` during SDK initialization: + +### Kotlin + +```kotlin +import com.mixpanel.android.mpmetrics.AutocaptureOptions +import com.mixpanel.android.mpmetrics.MixpanelAPI +import com.mixpanel.android.mpmetrics.MixpanelOptions + +val autocaptureOptions = AutocaptureOptions.Builder().build() + +val options = MixpanelOptions.Builder() + .autocaptureOptions(autocaptureOptions) + .build() + +val mixpanel = MixpanelAPI.getInstance(context, "YOUR_TOKEN", true, options) +``` + +### Java + +```java +import com.mixpanel.android.mpmetrics.AutocaptureOptions; +import com.mixpanel.android.mpmetrics.MixpanelAPI; +import com.mixpanel.android.mpmetrics.MixpanelOptions; + +AutocaptureOptions autocaptureOptions = new AutocaptureOptions.Builder().build(); + +MixpanelOptions options = new MixpanelOptions.Builder() + .autocaptureOptions(autocaptureOptions) + .build(); + +MixpanelAPI mixpanel = MixpanelAPI.getInstance(context, "YOUR_TOKEN", true, options); +``` + +That's it! No additional setup required. Autocapture automatically intercepts all touch events. + +## Configuration Options + +### ClickOptions + +| Option | Default | Description | +|--------|---------|-------------| +| `enabled` | `true` | Track all click events | + +### RageClickOptions + +| Option | Default | Description | +|--------|---------|-------------| +| `enabled` | `true` | Track rage click events | +| `clickThreshold` | `4` | Number of clicks required to trigger | +| `timeWindowMs` | `1000` | Time window in milliseconds | +| `radius` | `44` | Spatial threshold in dp (density-independent pixels) | + +### DeadClickOptions + +| Option | Default | Description | +|--------|---------|-------------| +| `enabled` | `true` | Track dead click events | +| `timeoutMs` | `500` | Response wait time in milliseconds | + +### Custom Configuration Example + +#### Kotlin + +```kotlin +val autocaptureOptions = AutocaptureOptions.Builder() + .clickOptions(ClickOptions.Builder().enabled(true).build()) + .rageClickOptions( + RageClickOptions.Builder() + .enabled(true) + .clickThreshold(5) // Require 5 clicks instead of 4 + .timeWindowMs(800) // Shorter time window + .radius(50f) // Larger radius + .build() + ) + .deadClickOptions( + DeadClickOptions.Builder() + .enabled(false) // Disable dead click detection + .build() + ) + .build() + +val options = MixpanelOptions.Builder() + .autocaptureOptions(autocaptureOptions) + .build() +``` + +#### Java + +```java +AutocaptureOptions autocaptureOptions = new AutocaptureOptions.Builder() + .clickOptions(new ClickOptions.Builder().enabled(true).build()) + .rageClickOptions( + new RageClickOptions.Builder() + .enabled(true) + .clickThreshold(5) // Require 5 clicks instead of 4 + .timeWindowMs(800) // Shorter time window + .radius(50f) // Larger radius + .build() + ) + .deadClickOptions( + new DeadClickOptions.Builder() + .enabled(false) // Disable dead click detection + .build() + ) + .build(); + +MixpanelOptions options = new MixpanelOptions.Builder() + .autocaptureOptions(autocaptureOptions) + .build(); +``` + +## Event Properties + +All autocapture events include these properties: + +| Property | Description | +|----------|-------------| +| `$x` | Touch X coordinate (screen pixels) | +| `$y` | Touch Y coordinate (screen pixels) | +| `$el_id` | Element identifier (see resolution rules below) | +| `$el_tag_name` | Class name of the view (e.g., `Button`, `TextView`) | +| `$attr-aria-label` | Content description (accessibility label) | +| `$attr-role` | Element role (Button, Switch, etc.) | +| `$elements` | View hierarchy string (max 5 levels) | + +## Element Identification (`$el_id`) + +The `$el_id` property uses the following resolution order: + +### Resolution Order + +1. `contentDescription` (if non-empty) +2. Resource ID name (e.g., `R.id.checkout_button` → `"checkout_button"`) +3. `ClassName_view_` (fallback) + +### Best Practices + +#### XML Views + +```xml + +