diff --git a/OACP_INTEGRATION.md b/OACP_INTEGRATION.md
new file mode 100644
index 00000000..349a3161
--- /dev/null
+++ b/OACP_INTEGRATION.md
@@ -0,0 +1,122 @@
+# OACP Integration — Fossify Voice Recorder
+
+How voice control was added to Fossify Voice Recorder using the OACP Kotlin SDK.
+
+## Overview
+
+This integration allows users to control Voice Recorder via the Hark voice assistant. Five capabilities are exposed:
+
+| Capability | Dispatch | What it does |
+|-----------|----------|-------------|
+| `start_recording` | **activity** | Opens the app and starts recording |
+| `pause_recording` | broadcast | Pauses active recording (background) |
+| `resume_recording` | broadcast | Resumes paused recording (background) |
+| `stop_recording` | broadcast | Stops and saves recording (background) |
+| `discard_recording` | broadcast | Discards recording without saving (background) |
+
+## Why two dispatch types?
+
+Android 14+ blocks `startActivity()` from BroadcastReceivers (Background Activity Launch restrictions). So:
+
+- **`start_recording`** needs to open the app UI, so it uses `type=activity`. Hark calls `startActivity()` directly from the foreground — no BAL restrictions.
+- **`pause/resume/stop/discard`** don't need UI — they work in the background via `type=broadcast`. The `OacpReceiver` from the SDK handles these.
+
+## Files added
+
+| File | Purpose |
+|------|---------|
+| `app/libs/oacp-android-release.aar` | OACP Kotlin SDK |
+| `app/src/main/assets/oacp.json` | Capability manifest (5 capabilities with rich metadata for embedding-based matching) |
+| `app/src/main/assets/OACP.md` | LLM context for disambiguation |
+| `app/src/main/kotlin/.../oacp/OacpActionReceiver.kt` | Broadcast handler for background actions |
+
+## Files modified
+
+### `app/build.gradle.kts`
+```kotlin
+dependencies {
+ implementation(files("libs/oacp-android-release.aar"))
+ implementation("androidx.annotation:annotation:1.7.1")
+}
+```
+
+### `app/src/main/AndroidManifest.xml`
+
+Activity intent filter on MainActivity (for `start_recording`):
+```xml
+
+
+
+
+
+
+
+```
+
+Receiver registration (for background actions):
+```xml
+
+
+
+
+
+
+
+
+```
+
+### `app/src/main/kotlin/.../activities/MainActivity.kt`
+
+Handle OACP intent in `onCreate()` (fresh launch) and `onNewIntent()` (singleTask redelivery):
+
+```kotlin
+// In onCreate(), after permission check:
+val shouldAutoRecord = config.recordAfterLaunch ||
+ intent?.action?.endsWith(".oacp.ACTION_START_RECORDING") == true
+
+if (shouldAutoRecord && !RecorderService.isRunning) {
+ Intent(this, RecorderService::class.java).apply {
+ try { startService(this) } catch (_: Exception) { }
+ }
+}
+```
+
+```kotlin
+override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ handleOacpIntent()
+}
+
+private fun handleOacpIntent() {
+ if (intent?.action?.endsWith(".oacp.ACTION_START_RECORDING") != true) return
+ intent?.action = null // consume so it doesn't re-trigger
+ binding.viewPager.currentItem = 0 // switch to recorder tab
+ if (RecorderService.isRunning) return
+ Intent(this, RecorderService::class.java).apply {
+ try { startService(this) } catch (_: Exception) { }
+ }
+}
+```
+
+**Key detail:** `onNewIntent()` is critical for `singleTask` activities. Without it, the OACP intent is silently dropped when the activity is already in memory.
+
+## Testing with adb
+
+```bash
+# Verify capabilities are served
+adb shell content read --uri "content://org.fossify.voicerecorder.debug.oacp/manifest"
+
+# Test activity dispatch (opens app)
+adb shell am start -a org.fossify.voicerecorder.debug.oacp.ACTION_START_RECORDING \
+ -n org.fossify.voicerecorder.debug/org.fossify.voicerecorder.activities.MainActivity
+
+# Test broadcast dispatch (background)
+adb shell am broadcast -a org.fossify.voicerecorder.debug.oacp.ACTION_PAUSE_RECORDING \
+ -p org.fossify.voicerecorder.debug
+```
+
+## SDK auto-registration
+
+The `OacpProvider` ContentProvider is auto-registered via Android manifest merger from the SDK. No manual registration needed. It serves `oacp.json` and `OACP.md` at `content://${applicationId}.oacp/manifest` and `content://${applicationId}.oacp/context`.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 238ea798..de25fc34 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -147,4 +147,6 @@ dependencies {
implementation(libs.tandroidlame)
implementation(libs.autofittextview)
detektPlugins(libs.compose.detekt)
+ implementation(files("libs/oacp-android-release.aar"))
+ implementation("androidx.annotation:annotation:1.7.1")
}
diff --git a/app/libs/oacp-android-release.aar b/app/libs/oacp-android-release.aar
new file mode 100644
index 00000000..3e8af365
Binary files /dev/null and b/app/libs/oacp-android-release.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fdb2017a..e496dfd7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -77,6 +77,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OacpResult.success("Recording paused")
+ action.endsWith(".oacp.ACTION_RESUME_RECORDING") ->
+ OacpResult.success("Recording resumed")
+ action.endsWith(".oacp.ACTION_STOP_RECORDING") ->
+ OacpResult.success("Recording stopped and saved")
+ action.endsWith(".oacp.ACTION_DISCARD_RECORDING") ->
+ OacpResult.success("Recording discarded")
+ else -> null
+ }
+ }
+}