Skip to content

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:publishToMavenLocal

Then 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 seconds

Callbacks ​

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.

PropertyTypeDefaultDescription
embedTokenString?nullHorus project embed token (starts with proj_)
recordingLinkIdString?nullLegacy: Direct recording link token
apiUrlStringhttps://tryhorus.ioHorus API URL
userInfoUserInfo?nullUser identification
customMetadataMap<String, Any>?nullCustom key-value pairs
captureAudioBooleantrueWhether to capture microphone audio
videoWidthInt720Video width in pixels
videoHeightInt1280Video height in pixels
videoBitrateInt2,000,000Video bitrate in bps
videoFpsInt30Video 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 ​

  1. Make sure you're not recording DRM-protected content
  2. Check that the MediaProjection permission was granted
  3. Verify the device supports screen capture

Audio not being recorded ​

  1. Request RECORD_AUDIO permission at runtime first
  2. Ensure captureAudio = true in config
  3. Check that no other app is using the microphone

Upload fails with network error ​

  1. Verify internet connectivity
  2. Check that the embedToken is valid
  3. Ensure the Horus API URL is reachable

Recording stops immediately ​

  1. Check for errors in the onError callback
  2. Verify the app has sufficient storage space
  3. Ensure the foreground service is running

Released under the MIT License.