Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion e2e/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:name=".BaseApplication"
android:allowBackup="true"
Expand All @@ -19,10 +21,15 @@
android:exported="false"
android:label="@string/title_activity_secondary"
android:theme="@style/Theme.AndroidObservability" />
<service
android:name=".ObservabilityForegroundService"
android:exported="false" />
<service
android:name=".ObservabilityBackgroundService"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AndroidObservability" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ class MainActivity : ComponentActivity() {
) {
Text("Trigger Log")
}
Button(
onClick = {
viewModel.startForegroundService()
}
) {
Text("Start Foreground Service")
}
Button(
onClick = {
viewModel.startBackgroundService()
}
) {
Text("Start Background Service")
}
Button(
onClick = {
viewModel.triggerNestedSpans()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.androidobservability

import android.app.Service
import android.content.Intent
import android.os.IBinder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel

class ObservabilityBackgroundService : Service() {

private val serviceScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private var loggingJob: Job? = null

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
loggingJob?.cancel()
loggingJob = serviceScope.launchObservabilityLoggingTask(serviceType = "background") {
stopSelf(startId)
}

return START_NOT_STICKY
}

override fun onDestroy() {
loggingJob?.cancel()
serviceScope.cancel()
super.onDestroy()
}

override fun onBind(intent: Intent?): IBinder? = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.example.androidobservability

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel

private const val FOREGROUND_NOTIFICATION_ID = 1001
private const val FOREGROUND_CHANNEL_ID = "observability_foreground"

class ObservabilityForegroundService : Service() {

private val serviceScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private var loggingJob: Job? = null

override fun onCreate() {
super.onCreate()
createNotificationChannel()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(FOREGROUND_NOTIFICATION_ID, buildNotification())

loggingJob?.cancel()
loggingJob = serviceScope.launchObservabilityLoggingTask(serviceType = "foreground") {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf(startId)
}

return START_NOT_STICKY
}

override fun onDestroy() {
loggingJob?.cancel()
serviceScope.cancel()
super.onDestroy()
}

override fun onBind(intent: Intent?): IBinder? = null

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
FOREGROUND_CHANNEL_ID,
"Observability Foreground Service",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Displays status while the observability foreground service is running"
}

val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(channel)
}
}

private fun buildNotification(): Notification {
return NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Foreground logging in progress")
.setContentText("Sending observability logs every 5 seconds for 30 seconds.")
.setOngoing(true)
.setSilent(true)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.example.androidobservability

import android.util.Log
import com.launchdarkly.observability.sdk.LDObserve
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.logs.Severity
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

private const val TOTAL_DURATION_MS = 30_000L
private const val TICK_INTERVAL_MS = 5_000L
private const val TOTAL_TICKS = (TOTAL_DURATION_MS / TICK_INTERVAL_MS).toInt()

/**
* Launches a 30-second logging routine.
*/
fun CoroutineScope.launchObservabilityLoggingTask(
serviceType: String,
onComplete: () -> Unit
): Job {
val serviceAttribute = AttributeKey.stringKey("service_type")

return launch {
try {
Log.d("observability-services", "Starting $serviceType service logging task")
LDObserve.recordLog(
message = "Starting $serviceType service logging task",
severity = Severity.INFO,
attributes = Attributes.of(serviceAttribute, serviceType)
)

repeat(TOTAL_TICKS) { index ->
delay(TICK_INTERVAL_MS)
val attributes = Attributes.builder()
.put(serviceAttribute, serviceType)
.put(AttributeKey.longKey("tick_index"), (index + 1).toLong())
.put(AttributeKey.longKey("elapsed_ms"), ((index + 1) * TICK_INTERVAL_MS))
.build()

Log.d("observability-services", "[$serviceType] Heartbeat ${index + 1} of $TOTAL_TICKS")
LDObserve.recordLog(
message = "[$serviceType] Heartbeat ${index + 1} of $TOTAL_TICKS",
severity = Severity.INFO,
attributes = attributes
)
}

Log.d("observability-services", "$serviceType service logging task completed")
LDObserve.recordLog(
message = "$serviceType service logging task completed",
severity = Severity.INFO,
attributes = Attributes.of(serviceAttribute, serviceType)
)

onComplete()
} catch (_: CancellationException) {
Log.d("observability-services", "$serviceType service logging task cancelled")
LDObserve.recordLog(
message = "$serviceType service logging task cancelled",
severity = Severity.INFO,
attributes = Attributes.of(serviceAttribute, serviceType)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.example.androidobservability

import androidx.lifecycle.ViewModel
import android.app.Application
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.launchdarkly.observability.interfaces.Metric
import com.launchdarkly.observability.sdk.LDObserve
Expand All @@ -18,7 +21,7 @@ import java.io.BufferedInputStream
import java.net.HttpURLConnection
import java.net.URL

class ViewModel : ViewModel() {
class ViewModel(application: Application) : AndroidViewModel(application) {

fun triggerMetric() {
LDObserve.recordMetric(Metric("test-gauge", 50.0))
Expand Down Expand Up @@ -121,6 +124,16 @@ class ViewModel : ViewModel() {
LDClient.get().identify(context)
}

fun startForegroundService() {
val intent = Intent(getApplication(), ObservabilityForegroundService::class.java)
ContextCompat.startForegroundService(getApplication(), intent)
}

fun startBackgroundService() {
val intent = Intent(getApplication(), ObservabilityBackgroundService::class.java)
getApplication<Application>().startService(intent)
}

private fun sendOkHttpRequest() {
// Create HTTP client
val client = OkHttpClient()
Expand Down
Loading