Skip to content

Commit

Permalink
Create annotation to opt into activity open tracing
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Nov 29, 2024
1 parent d320798 commit 3ba7d00
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 12 deletions.
3 changes: 3 additions & 0 deletions embrace-android-api/api/embrace-android-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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])
Expand Down Expand Up @@ -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 <T : Activity> setupActivityController(activityClass: KClass<T>) {
activityController = Robolectric.buildActivity(activityClass.java)
instanceId = activityController.get().hashCode()
activityName = activityController.get().localClassName
}

private fun stepThroughActivityLifecycle(
isColdOpen: Boolean = true
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 3ba7d00

Please sign in to comment.