Android SDK
Native Android SDK for screen recording and feedback collection.
Requirements
- Android 7.0 (API 24) or higher
- Kotlin 1.9+ or Java 11+
- Android Studio Hedgehog (2023.1.1) or later
Installation
Gradle (Kotlin DSL)
Add the dependency to your app's build.gradle.kts:
kotlin
dependencies {
implementation("io.horus:horus-sdk:1.0.0")
}Gradle (Groovy)
groovy
dependencies {
implementation 'io.horus:horus-sdk:1.0.0'
}Local Development
To build and use the SDK locally:
bash
cd android
./gradlew :horus-sdk:publishToMavenLocalThen add mavenLocal() to your repositories:
kotlin
repositories {
mavenLocal()
// ... other repositories
}Permissions
The SDK requires these permissions (automatically merged from the library manifest):
xml
<!-- Required for screen recording (Android 10+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<!-- Required for audio recording -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Required for network status detection -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Required for uploading recordings -->
<uses-permission android:name="android.permission.INTERNET" />Runtime Permissions
You must request RECORD_AUDIO permission at runtime before starting a recording with audio:
kotlin
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
AUDIO_PERMISSION_REQUEST_CODE
)
}Quick Start
1. Initialize the SDK
Initialize in your Application class:
kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
HorusSDK.init(
context = this,
config = HorusConfig(
embedToken = "proj_your_token_here",
userInfo = UserInfo(
email = "user@example.com",
name = "John Doe",
userId = "user_123"
),
customMetadata = mapOf(
"plan" to "premium",
"company" to "Acme Inc"
)
)
)
}
}2. Request Screen Capture Permission
From your Activity:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Setup callbacks
HorusSDK.setOnRecordingStarted {
runOnUiThread { showRecordingUI() }
}
HorusSDK.setOnRecordingStopped { result ->
runOnUiThread { showUploadingUI() }
}
HorusSDK.setOnUploadProgress { progress ->
runOnUiThread { updateProgress(progress) }
}
HorusSDK.setOnUploadComplete { result ->
runOnUiThread {
Toast.makeText(this, "Recording uploaded!", Toast.LENGTH_SHORT).show()
}
}
HorusSDK.setOnError { error ->
runOnUiThread {
Toast.makeText(this, "Error: ${error.message}", Toast.LENGTH_LONG).show()
}
}
}
fun startRecording() {
// This shows a system dialog asking the user for permission
HorusSDK.requestScreenCapture(
activity = this,
includeAudio = true
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Let the SDK handle the permission result
if (HorusSDK.onActivityResult(requestCode, resultCode, data)) {
// Recording started successfully
return
}
}
fun stopRecording() {
HorusSDK.stopRecording(
description = "Bug in checkout flow",
metadata = mapOf(
"screen" to "checkout",
"action" to "payment_failed"
)
)
}
}API Reference
HorusSDK
The main SDK singleton.
Initialization
kotlin
HorusSDK.init(context: Context, config: HorusConfig)Initialize the SDK. Must be called before any other methods.
Recording Control
kotlin
// Request screen capture permission (shows system dialog)
HorusSDK.requestScreenCapture(activity: Activity, includeAudio: Boolean = true)
// Handle activity result (call from onActivityResult)
HorusSDK.onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean
// Stop recording and upload
HorusSDK.stopRecording(
description: String? = null,
metadata: Map<String, Any>? = null
)
// Cancel recording without uploading
HorusSDK.cancelRecording()Properties
kotlin
HorusSDK.isInitialized(): Boolean // Check if SDK is initialized
HorusSDK.isRecording(): Boolean // Check if recording is in progress
HorusSDK.getRecordingDuration(): Double // Get current duration in secondsCallbacks
kotlin
HorusSDK.setOnRecordingStarted { /* Recording started */ }
HorusSDK.setOnRecordingStopped { result: RecordingResult -> /* Before upload */ }
HorusSDK.setOnUploadProgress { progress: Float -> /* 0.0 to 1.0 */ }
HorusSDK.setOnUploadComplete { result: UploadResult -> /* Upload done */ }
HorusSDK.setOnError { error: HorusException -> /* Handle error */ }User Management
kotlin
// Update user info after initialization
HorusSDK.setUserInfo(UserInfo(email = "new@example.com"))
// Reset the SDK
HorusSDK.reset()HorusConfig
Configuration options for the SDK.
| Property | Type | Default | Description |
|---|---|---|---|
embedToken | String? | null | Horus project embed token (starts with proj_) |
recordingLinkId | String? | null | Legacy: Direct recording link token |
apiUrl | String | https://tryhorus.io | Horus API URL |
userInfo | UserInfo? | null | User identification |
customMetadata | Map<String, Any>? | null | Custom key-value pairs |
captureAudio | Boolean | true | Whether to capture microphone audio |
videoWidth | Int | 720 | Video width in pixels |
videoHeight | Int | 1280 | Video height in pixels |
videoBitrate | Int | 2,000,000 | Video bitrate in bps |
videoFps | Int | 30 | Video frames per second |
UserInfo
kotlin
data class UserInfo(
val email: String? = null,
val name: String? = null,
val userId: String? = null
)RecordingResult
Returned after recording stops (before upload).
kotlin
data class RecordingResult(
val videoFile: File,
val audioFile: File?,
val duration: Double, // seconds
val description: String?
)UploadResult
Returned after successful upload.
kotlin
data class UploadResult(
val recordingId: String,
val streamVideoId: String?,
val success: Boolean,
val message: String?
)HorusException
Error types thrown by the SDK.
kotlin
sealed class HorusException(message: String) : Exception(message) {
class NotInitialized(message: String) : HorusException(message)
class PermissionDenied(message: String) : HorusException(message)
class RecordingFailed(message: String) : HorusException(message)
class UploadFailed(message: String) : HorusException(message)
class InvalidConfig(message: String) : HorusException(message)
class NetworkError(message: String) : HorusException(message)
}Recording UI Example
Show a recording indicator while recording:
kotlin
class RecordingOverlayView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private val durationText: TextView
private var startTime: Long = 0
private val handler = Handler(Looper.getMainLooper())
init {
inflate(context, R.layout.recording_overlay, this)
durationText = findViewById(R.id.duration_text)
}
fun startTimer() {
startTime = System.currentTimeMillis()
updateDuration()
}
private fun updateDuration() {
val elapsed = (System.currentTimeMillis() - startTime) / 1000
val minutes = elapsed / 60
val seconds = elapsed % 60
durationText.text = String.format("%02d:%02d", minutes, seconds)
handler.postDelayed({ updateDuration() }, 1000)
}
fun stopTimer() {
handler.removeCallbacksAndMessages(null)
}
}ProGuard / R8 Configuration
If you're using ProGuard or R8, add these rules:
proguard
-keep class io.horus.sdk.** { *; }
-dontwarn io.horus.sdk.**Troubleshooting
Screen appears black in recording
- Make sure you're not recording DRM-protected content
- Check that the MediaProjection permission was granted
- Verify the device supports screen capture
Audio not being recorded
- Request
RECORD_AUDIOpermission at runtime first - Ensure
captureAudio = truein config - Check that no other app is using the microphone
Upload fails with network error
- Verify internet connectivity
- Check that the
embedTokenis valid - Ensure the Horus API URL is reachable
Recording stops immediately
- Check for errors in the
onErrorcallback - Verify the app has sufficient storage space
- Ensure the foreground service is running