From 19a8def29c063e7fc7e02955943314d1f3336efe 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 --- .../embracesdk/annotation/ObservedActivity.kt | 9 ++++ .../capture/activity/OpenEventEmitter.kt | 33 ++++++++---- .../capture/activity/OpenEventEmitterTest.kt | 50 +++++++++++++++++-- .../embracesdk/fakes/FakeObservedActivity.kt | 7 +++ 4 files changed, 87 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/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/OpenEventEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitter.kt index 5f63705c6d..e3d2aa4b93 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitter.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 OpenEventEmitter( ) : 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) { @@ -148,4 +161,6 @@ class OpenEventEmitter( 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/OpenEventEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitterTest.kt index 33af2fdb94..7e02e61b39 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenEventEmitterTest.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 OpenEventEmitterTest { @@ -41,14 +43,12 @@ internal class OpenEventEmitterTest { 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]) @@ -199,6 +199,50 @@ internal class OpenEventEmitterTest { ) } + @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 = "resetTrace", + timestampMs = startTimeMs + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 3 + PRE_DURATION + ), + createEvent( + stage = "hibernate", + timestampMs = startTimeMs + (POST_DURATION + STATE_DURATION + PRE_DURATION) * 4 + STATE_DURATION + ), + ) + ) + } + + @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 = "resetTrace", + timestampMs = startTimeMs + STATE_DURATION * 4 + ), + createEvent( + stage = "hibernate", + timestampMs = startTimeMs + STATE_DURATION * 5 + ), + ) + ) + } + + 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()