From 3ba7d0072845e587edf0a59b4085aed777070ba3 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Tue, 10 Sep 2024 12:56:37 -0700 Subject: [PATCH] Create annotation to opt into activity open tracing --- .../api/embrace-android-api.api | 3 ++ .../embracesdk/annotation/ObservedActivity.kt | 9 ++++ .../capture/activity/UiLoadEventEmitter.kt | 33 +++++++++---- .../activity/UiLoadEventEmitterTest.kt | 48 +++++++++++++++++-- .../embracesdk/fakes/FakeObservedActivity.kt | 7 +++ 5 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/ObservedActivity.kt create mode 100644 embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeObservedActivity.kt diff --git a/embrace-android-api/api/embrace-android-api.api b/embrace-android-api/api/embrace-android-api.api index 027f8aa9a8..11aa647145 100644 --- a/embrace-android-api/api/embrace-android-api.api +++ b/embrace-android-api/api/embrace-android-api.api @@ -27,6 +27,9 @@ public final class io/embrace/android/embracesdk/Severity : java/lang/Enum { public abstract interface annotation class io/embrace/android/embracesdk/annotation/InternalApi : java/lang/annotation/Annotation { } +public abstract interface annotation class io/embrace/android/embracesdk/annotation/ObservedActivity : java/lang/annotation/Annotation { +} + public abstract interface annotation class io/embrace/android/embracesdk/annotation/StartupActivity : java/lang/annotation/Annotation { } diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/ObservedActivity.kt b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/ObservedActivity.kt new file mode 100644 index 0000000000..358dfdacbd --- /dev/null +++ b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/ObservedActivity.kt @@ -0,0 +1,9 @@ +package io.embrace.android.embracesdk.annotation + +import java.lang.annotation.Inherited + +@MustBeDocumented +@Target(AnnotationTarget.CLASS) +@Inherited +@Retention(AnnotationRetention.RUNTIME) +public annotation class ObservedActivity diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitter.kt index 2c65af6198..dde220a08a 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitter.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.app.Application.ActivityLifecycleCallbacks import android.os.Build import android.os.Bundle +import io.embrace.android.embracesdk.annotation.ObservedActivity import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener import io.embrace.android.embracesdk.internal.utils.VersionChecker @@ -25,47 +26,59 @@ class UiLoadEventEmitter( ) : ActivityLifecycleListener { override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) { - create(activity) + if (activity.observeOpening()) { + create(activity) + } } override fun onActivityCreated(activity: Activity, bundle: Bundle?) { - if (!versionChecker.firePrePostEvents()) { + if (activity.observeOpening() && !versionChecker.firePrePostEvents()) { create(activity) } } override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) { - createEnd(activity) + if (activity.observeOpening()) { + createEnd(activity) + } } override fun onActivityPreStarted(activity: Activity) { - start(activity) + if (activity.observeOpening()) { + start(activity) + } } override fun onActivityStarted(activity: Activity) { - if (!versionChecker.firePrePostEvents()) { + if (activity.observeOpening() && !versionChecker.firePrePostEvents()) { createEnd(activity) start(activity) } } override fun onActivityPostStarted(activity: Activity) { - startEnd(activity) + if (activity.observeOpening()) { + startEnd(activity) + } } override fun onActivityPreResumed(activity: Activity) { - resume(activity) + if (activity.observeOpening()) { + resume(activity) + } } override fun onActivityResumed(activity: Activity) { - if (!versionChecker.firePrePostEvents()) { + if (activity.observeOpening() && !versionChecker.firePrePostEvents()) { startEnd(activity) resume(activity) } } override fun onActivityPostResumed(activity: Activity) { - resumeEnd(activity) + if (activity.observeOpening()) { + resumeEnd(activity) + } } override fun onActivityPrePaused(activity: Activity) { @@ -146,4 +159,6 @@ class UiLoadEventEmitter( private fun traceInstanceId(activity: Activity): Int = activity.hashCode() private fun nowMs(): Long = clock.now().nanosToMillis() + + private fun Activity.observeOpening() = javaClass.isAnnotationPresent(ObservedActivity::class.java) } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitterTest.kt index 82e20dac85..02900d9a73 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEventEmitterTest.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.FakeObservedActivity import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.ClockTickingActivityLifecycleCallbacks import io.embrace.android.embracesdk.internal.ClockTickingActivityLifecycleCallbacks.Companion.POST_DURATION @@ -19,6 +20,7 @@ import org.robolectric.Robolectric import org.robolectric.RuntimeEnvironment import org.robolectric.android.controller.ActivityController import org.robolectric.annotation.Config +import kotlin.reflect.KClass @RunWith(AndroidJUnit4::class) internal class UiLoadEventEmitterTest { @@ -41,14 +43,12 @@ internal class UiLoadEventEmitterTest { clock = initModule.openTelemetryModule.openTelemetryClock, versionChecker = BuildVersionChecker, ) - activityController = Robolectric.buildActivity(Activity::class.java) RuntimeEnvironment.getApplication().registerActivityLifecycleCallbacks( ClockTickingActivityLifecycleCallbacks(clock) ) RuntimeEnvironment.getApplication().registerActivityLifecycleCallbacks(eventEmitter) startTimeMs = clock.now() - instanceId = activityController.get().hashCode() - activityName = activityController.get().localClassName + setupActivityController(FakeObservedActivity::class) } @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) @@ -195,6 +195,48 @@ internal class UiLoadEventEmitterTest { ) } + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `unobserved activities will not emit open events in U`() { + setupActivityController(Activity::class) + stepThroughActivityLifecycle() + openEvents.events.assertEventData( + listOf( + createEvent( + stage = "abandon", + timestampMs = startTimeMs + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + PRE_DURATION + ), + createEvent( + stage = "reset", + ), + ) + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `unobserved activities will not emit open events in L`() { + setupActivityController(Activity::class) + stepThroughActivityLifecycle() + openEvents.events.assertEventData( + listOf( + createEvent( + stage = "abandon", + timestampMs = startTimeMs + STATE_DURATION * 4 + ), + createEvent( + stage = "reset", + ), + ) + ) + } + + private fun setupActivityController(activityClass: KClass) { + activityController = Robolectric.buildActivity(activityClass.java) + instanceId = activityController.get().hashCode() + activityName = activityController.get().localClassName + } + private fun stepThroughActivityLifecycle( isColdOpen: Boolean = true ) { diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeObservedActivity.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeObservedActivity.kt new file mode 100644 index 0000000000..db86c9d88f --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeObservedActivity.kt @@ -0,0 +1,7 @@ +package io.embrace.android.embracesdk.fakes + +import android.app.Activity +import io.embrace.android.embracesdk.annotation.ObservedActivity + +@ObservedActivity +class FakeObservedActivity : Activity()