diff --git a/demo-app/build.gradle.kts b/demo-app/build.gradle.kts
index a5b417a45..967f851fe 100644
--- a/demo-app/build.gradle.kts
+++ b/demo-app/build.gradle.kts
@@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(rootLibs.plugins.androidApp)
alias(libs.plugins.compose.compiler)
+ alias(libs.plugins.byteBuddy)
}
android {
@@ -69,6 +70,9 @@ dependencies {
implementation("io.opentelemetry.android:android-agent") //parent dir
implementation("io.opentelemetry.android.instrumentation:compose-click")
implementation("io.opentelemetry.android.instrumentation:sessions")
+ implementation("io.opentelemetry.android.instrumentation:okhttp3-library")
+ byteBuddy("io.opentelemetry.android.instrumentation:okhttp3-agent")
+ implementation(libs.okhttp)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
diff --git a/demo-app/gradle/libs.versions.toml b/demo-app/gradle/libs.versions.toml
index 8b4945fbc..710d3bd64 100644
--- a/demo-app/gradle/libs.versions.toml
+++ b/demo-app/gradle/libs.versions.toml
@@ -1,4 +1,6 @@
[versions]
+byteBuddy = "1.18.8"
+okhttp = "5.3.2"
opentelemetry = "1.61.0"
opentelemetry-alpha = "1.61.0-alpha"
junit = "6.0.3"
@@ -11,6 +13,7 @@ androidx-appcompat = "androidx.appcompat:appcompat:1.7.1"
opentelemetry-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "opentelemetry" }
opentelemetry-api-incubator = { module = "io.opentelemetry:opentelemetry-api-incubator", version.ref = "opentelemetry-alpha" }
gson = "com.google.code.gson:gson:2.13.2"
+okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
#Test tools
androidx-junit = "androidx.test.ext:junit:1.3.0"
@@ -47,3 +50,4 @@ junit = ["junit-jupiter-api", "junit-jupiter-engine", "junit-vintage-engine"]
[plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+byteBuddy = { id = "net.bytebuddy.byte-buddy-gradle-plugin", version.ref = "byteBuddy" }
diff --git a/demo-app/settings.gradle.kts b/demo-app/settings.gradle.kts
index ccd4628fb..3e6cbc24b 100644
--- a/demo-app/settings.gradle.kts
+++ b/demo-app/settings.gradle.kts
@@ -28,5 +28,9 @@ includeBuild("..") {
.using(project(":instrumentation:compose:click"))
substitute(module("io.opentelemetry.android.instrumentation:sessions"))
.using(project(":instrumentation:sessions"))
+ substitute(module("io.opentelemetry.android.instrumentation:okhttp3-library"))
+ .using(project(":instrumentation:okhttp3:library"))
+ substitute(module("io.opentelemetry.android.instrumentation:okhttp3-agent"))
+ .using(project(":instrumentation:okhttp3:agent"))
}
}
\ No newline at end of file
diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml
index 6795d2986..f3d8ae718 100644
--- a/demo-app/src/main/AndroidManifest.xml
+++ b/demo-app/src/main/AndroidManifest.xml
@@ -29,6 +29,12 @@
android:exported="true"
android:label="About OpenTelemetry Android"
/>
+
Unit) {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ val client = remember { OkHttpClient() }
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ TopAppBar(
+ title = { Text("Instrumentation testing") },
+ navigationIcon = {
+ IconButton(onClick = onBack) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = "Back",
+ )
+ }
+ },
+ )
+ },
+ ) { innerPadding ->
+ Column(
+ modifier =
+ Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ .verticalScroll(rememberScrollState())
+ .padding(horizontal = 20.dp, vertical = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ Text(
+ text = "OkHttp sample calls (GET, POST, delayed GET); each should produce a traced client span in your collector.",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ OkHttpDemoActionButton(
+ onClick = {
+ scope.launch {
+ val msg =
+ runCatching {
+ withContext(Dispatchers.IO) {
+ val request =
+ Request.Builder().url("https://httpbin.org/status/200").get().build()
+ val code = client.newCall(request).execute().use { it.code }
+ "OkHttp: HTTP $code"
+ }
+ }.getOrElse { e -> "OkHttp 200 demo: ${e.message}" }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+ },
+ text = "GET known 200",
+ )
+ OkHttpDemoActionButton(
+ onClick = {
+ scope.launch {
+ val msg =
+ runCatching {
+ withContext(Dispatchers.IO) {
+ val request =
+ Request.Builder().url("https://httpbin.org/status/404").get().build()
+ val code = client.newCall(request).execute().use { it.code }
+ "OkHttp: HTTP $code"
+ }
+ }.getOrElse { e -> "OkHttp 404 demo: ${e.message}" }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+ },
+ text = "GET known 404",
+ )
+ OkHttpDemoActionButton(
+ onClick = {
+ scope.launch {
+ val msg =
+ runCatching {
+ withContext(Dispatchers.IO) {
+ val request =
+ Request.Builder().url("http://127.0.0.1:1/").get().build()
+ client.newCall(request).execute().use { it.code }
+ "OkHttp: unexpected success"
+ }
+ }.getOrElse { e -> "OkHttp failure demo: ${e.javaClass.simpleName}" }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+ },
+ text = "GET connection failure",
+ )
+ OkHttpDemoActionButton(
+ onClick = {
+ scope.launch {
+ val msg =
+ runCatching {
+ withContext(Dispatchers.IO) {
+ val json = """{"event":"otel-android-demo","source":"okhttp"}"""
+ val body =
+ json.toRequestBody("application/json; charset=utf-8".toMediaType())
+ val request =
+ Request.Builder().url("https://httpbin.org/post").post(body).build()
+ val code = client.newCall(request).execute().use { it.code }
+ "OkHttp POST: HTTP $code"
+ }
+ }.getOrElse { e -> "OkHttp POST demo: ${e.message}" }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+ },
+ text = "POST JSON payload",
+ )
+ OkHttpDemoActionButton(
+ onClick = {
+ scope.launch {
+ val msg =
+ runCatching {
+ withContext(Dispatchers.IO) {
+ val request =
+ Request.Builder().url("https://httpbin.org/delay/3").get().build()
+ val code = client.newCall(request).execute().use { it.code }
+ "OkHttp slow GET: HTTP $code (~3s)"
+ }
+ }.getOrElse { e -> "OkHttp slow demo: ${e.message}" }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+ },
+ text = "GET slow response (3s)",
+ )
+ }
+ }
+}
+
+@Composable
+private fun OkHttpDemoActionButton(
+ text: String,
+ onClick: () -> Unit,
+) {
+ OutlinedButton(
+ onClick = onClick,
+ modifier = Modifier.fillMaxWidth().height(56.dp),
+ border = BorderStroke(1.dp, DemoAccentBlue),
+ colors =
+ ButtonDefaults.outlinedButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = DemoAccentBlue,
+ ),
+ ) {
+ Text(text)
+ }
+}
diff --git a/demo-app/src/main/java/io/opentelemetry/android/demo/theme/Theme.kt b/demo-app/src/main/java/io/opentelemetry/android/demo/theme/Theme.kt
index 6604f1301..3377b5029 100644
--- a/demo-app/src/main/java/io/opentelemetry/android/demo/theme/Theme.kt
+++ b/demo-app/src/main/java/io/opentelemetry/android/demo/theme/Theme.kt
@@ -15,9 +15,11 @@ import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
+import androidx.core.graphics.ColorUtils
import androidx.core.view.WindowCompat
private val DarkColorScheme =
@@ -64,8 +66,13 @@ fun DemoAppTheme(
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
- window.statusBarColor = colorScheme.primary.toArgb()
- WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ val insetsController = WindowCompat.getInsetsController(window, view)
+ val backgroundArgb = colorScheme.background.toArgb()
+ window.statusBarColor = Color.Transparent.toArgb()
+ window.navigationBarColor = Color.Transparent.toArgb()
+ val useDarkSystemIcons = ColorUtils.calculateLuminance(backgroundArgb) > 0.5
+ insetsController.isAppearanceLightStatusBars = useDarkSystemIcons
+ insetsController.isAppearanceLightNavigationBars = useDarkSystemIcons
}
}