From 58636dd68e1e25562d1ca52a830f9fe141b985b5 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Thu, 2 Jan 2025 15:55:47 -0800 Subject: [PATCH] API and integration tests for manually ending UI load traces --- .../api/embrace-android-api.api | 9 +- .../annotation/CustomTracedActivity.kt | 9 ++ .../internal/api/InstrumentationApi.kt | 17 ++++ .../android/embracesdk/internal/api/SdkApi.kt | 3 +- .../internal/capture/activity/UiLoadExt.kt | 33 ++++--- .../capture/activity/UiLoadExtTest.kt | 17 ++++ .../api/embrace-android-sdk.api | 1 + .../testcases/features/UiLoadTest.kt | 88 +++++++++++++++++++ .../actions/EmbraceActionInterface.kt | 5 ++ .../io/embrace/android/embracesdk/Embrace.kt | 5 ++ .../embrace/android/embracesdk/EmbraceImpl.kt | 5 ++ .../delegate/InstrumentationApiDelegate.kt | 25 ++++++ .../fakes/FakeCustomTracedActivity.kt | 7 ++ 13 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/CustomTracedActivity.kt create mode 100644 embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/InstrumentationApi.kt create mode 100644 embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/InstrumentationApiDelegate.kt create mode 100644 embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeCustomTracedActivity.kt diff --git a/embrace-android-api/api/embrace-android-api.api b/embrace-android-api/api/embrace-android-api.api index b55f55dac9..52a5c24f29 100644 --- a/embrace-android-api/api/embrace-android-api.api +++ b/embrace-android-api/api/embrace-android-api.api @@ -24,6 +24,9 @@ public final class io/embrace/android/embracesdk/Severity : java/lang/Enum { public static fun values ()[Lio/embrace/android/embracesdk/Severity; } +public abstract interface annotation class io/embrace/android/embracesdk/annotation/CustomTracedActivity : java/lang/annotation/Annotation { +} + public abstract interface annotation class io/embrace/android/embracesdk/annotation/InternalApi : java/lang/annotation/Annotation { } @@ -48,6 +51,10 @@ public abstract interface class io/embrace/android/embracesdk/internal/api/Embra public abstract fun startView (Ljava/lang/String;)Z } +public abstract interface class io/embrace/android/embracesdk/internal/api/InstrumentationApi { + public abstract fun activityLoaded (Landroid/app/Activity;)V +} + public abstract interface class io/embrace/android/embracesdk/internal/api/InternalWebViewApi { public abstract fun logWebView (Ljava/lang/String;)V public abstract fun trackWebViewPerformance (Ljava/lang/String;Landroid/webkit/ConsoleMessage;)V @@ -82,7 +89,7 @@ public abstract interface class io/embrace/android/embracesdk/internal/api/OTelA public abstract fun getOpenTelemetry ()Lio/opentelemetry/api/OpenTelemetry; } -public abstract interface class io/embrace/android/embracesdk/internal/api/SdkApi : io/embrace/android/embracesdk/internal/api/BreadcrumbApi, io/embrace/android/embracesdk/internal/api/EmbraceAndroidApi, io/embrace/android/embracesdk/internal/api/InternalWebViewApi, io/embrace/android/embracesdk/internal/api/LogsApi, io/embrace/android/embracesdk/internal/api/NetworkRequestApi, io/embrace/android/embracesdk/internal/api/OTelApi, io/embrace/android/embracesdk/internal/api/SdkStateApi, io/embrace/android/embracesdk/internal/api/SessionApi, io/embrace/android/embracesdk/internal/api/UserApi, io/embrace/android/embracesdk/spans/TracingApi { +public abstract interface class io/embrace/android/embracesdk/internal/api/SdkApi : io/embrace/android/embracesdk/internal/api/BreadcrumbApi, io/embrace/android/embracesdk/internal/api/EmbraceAndroidApi, io/embrace/android/embracesdk/internal/api/InstrumentationApi, io/embrace/android/embracesdk/internal/api/InternalWebViewApi, io/embrace/android/embracesdk/internal/api/LogsApi, io/embrace/android/embracesdk/internal/api/NetworkRequestApi, io/embrace/android/embracesdk/internal/api/OTelApi, io/embrace/android/embracesdk/internal/api/SdkStateApi, io/embrace/android/embracesdk/internal/api/SessionApi, io/embrace/android/embracesdk/internal/api/UserApi, io/embrace/android/embracesdk/spans/TracingApi { } public final class io/embrace/android/embracesdk/internal/api/SdkApi$DefaultImpls { diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/CustomTracedActivity.kt b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/CustomTracedActivity.kt new file mode 100644 index 0000000000..bec70d168f --- /dev/null +++ b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/annotation/CustomTracedActivity.kt @@ -0,0 +1,9 @@ +package io.embrace.android.embracesdk.annotation + +/** + * The loading of Activities annotated with this class will generate traces if the feature is enabled, irrespective of + * the configured defaults. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +public annotation class CustomTracedActivity diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/InstrumentationApi.kt b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/InstrumentationApi.kt new file mode 100644 index 0000000000..b0e62aced4 --- /dev/null +++ b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/InstrumentationApi.kt @@ -0,0 +1,17 @@ +package io.embrace.android.embracesdk.internal.api + +import android.app.Activity +import io.embrace.android.embracesdk.annotation.InternalApi + +/** + * API to control and customize Embrace instrumentation + */ +@InternalApi +public interface InstrumentationApi { + + /** + * Notify the Embrace UI Load instrumentation that the given [Activity] instance has fully loaded, so its associated + * trace can be stopped + */ + public fun activityLoaded(activity: Activity) +} diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/SdkApi.kt b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/SdkApi.kt index abe3bcfa09..c6c931e8ae 100644 --- a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/SdkApi.kt +++ b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/api/SdkApi.kt @@ -17,4 +17,5 @@ public interface SdkApi : SdkStateApi, OTelApi, BreadcrumbApi, - InternalWebViewApi + InternalWebViewApi, + InstrumentationApi diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt index 74be62c05d..0a36c0261e 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.os.Build.VERSION_CODES import android.os.Bundle import androidx.annotation.RequiresApi +import io.embrace.android.embracesdk.annotation.CustomTracedActivity import io.embrace.android.embracesdk.annotation.NotTracedActivity import io.embrace.android.embracesdk.annotation.TracedActivity import io.embrace.android.embracesdk.internal.clock.nanosToMillis @@ -35,6 +36,11 @@ fun createActivityLoadEventEmitter( } } +/** + * Return an ID to identify the trace for the given [Activity] instance + */ +fun traceInstanceId(activity: Activity): Int = activity.hashCode() + /** * Implementation that works with Android 10+ APIs */ @@ -108,18 +114,18 @@ private class LifecycleEventEmitter( ) { fun create(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.create( instanceId = traceInstanceId(activity), activityName = activity.localClassName, timestampMs = nowMs(), - manualEnd = false, + manualEnd = activity.isManualEnd(), ) } } fun createEnd(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.createEnd( instanceId = traceInstanceId(activity), timestampMs = nowMs() @@ -128,18 +134,18 @@ private class LifecycleEventEmitter( } fun start(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.start( instanceId = traceInstanceId(activity), activityName = activity.localClassName, timestampMs = nowMs(), - manualEnd = false, + manualEnd = activity.isManualEnd(), ) } } fun startEnd(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.startEnd( instanceId = traceInstanceId(activity), timestampMs = nowMs() @@ -148,7 +154,7 @@ private class LifecycleEventEmitter( } fun resume(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.resume( instanceId = traceInstanceId(activity), timestampMs = nowMs() @@ -157,7 +163,7 @@ private class LifecycleEventEmitter( } fun resumeEnd(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.resumeEnd( instanceId = traceInstanceId(activity), timestampMs = nowMs() @@ -166,7 +172,7 @@ private class LifecycleEventEmitter( } fun pause(activity: Activity) { - if (activity.observe()) { + if (activity.traceLoad()) { uiLoadEventListener.discard( instanceId = traceInstanceId(activity), timestampMs = nowMs() @@ -174,12 +180,13 @@ private class LifecycleEventEmitter( } } - private fun Activity.observe(): Boolean { - return javaClass.isAnnotationPresent(TracedActivity::class.java) || - autoTraceEnabled && !javaClass.isAnnotationPresent(NotTracedActivity::class.java) + private fun Activity.traceLoad(): Boolean { + return (autoTraceEnabled && !javaClass.isAnnotationPresent(NotTracedActivity::class.java)) || + isManualEnd() || + javaClass.isAnnotationPresent(TracedActivity::class.java) } - private fun traceInstanceId(activity: Activity): Int = activity.hashCode() + private fun Activity.isManualEnd(): Boolean = javaClass.isAnnotationPresent(CustomTracedActivity::class.java) private fun nowMs(): Long = clock.now().nanosToMillis() } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt index a6d96812ec..bd940cd5ab 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt @@ -5,6 +5,7 @@ import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeClock.Companion.DEFAULT_FAKE_CURRENT_TIME +import io.embrace.android.embracesdk.fakes.FakeCustomTracedActivity import io.embrace.android.embracesdk.fakes.FakeNotTracedActivity import io.embrace.android.embracesdk.fakes.FakeTracedActivity import io.embrace.android.embracesdk.fakes.FakeUiLoadEventListener @@ -84,6 +85,14 @@ internal class UiLoadExtTest { assertTrue(uiLoadEventListener.events.isEmpty()) } + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `activities with manual ended trace will emit ui load events in U`() { + createEventEmitter(autoTraceEnabled = false, activityClass = FakeCustomTracedActivity::class) + stepThroughActivityLifecycle() + uiLoadEventListener.events.assertEventData(expectedColdOpenEvents) + } + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) @Test fun `check cold ui load event stages in L`() { @@ -124,6 +133,14 @@ internal class UiLoadExtTest { assertTrue(uiLoadEventListener.events.isEmpty()) } + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `activities with manual ended trace will emit ui load events in L`() { + createEventEmitter(autoTraceEnabled = false, activityClass = FakeCustomTracedActivity::class) + stepThroughActivityLifecycle() + uiLoadEventListener.events.assertEventData(legacyColdOpenEvents) + } + private fun createEventEmitter(autoTraceEnabled: Boolean, activityClass: KClass) { eventEmitter = createActivityLoadEventEmitter( uiLoadEventListener = uiLoadEventListener, diff --git a/embrace-android-sdk/api/embrace-android-sdk.api b/embrace-android-sdk/api/embrace-android-sdk.api index c40e75ee64..f1d279011a 100644 --- a/embrace-android-sdk/api/embrace-android-sdk.api +++ b/embrace-android-sdk/api/embrace-android-sdk.api @@ -1,5 +1,6 @@ public final class io/embrace/android/embracesdk/Embrace : io/embrace/android/embracesdk/internal/api/SdkApi { public static final field Companion Lio/embrace/android/embracesdk/Embrace$Companion; + public fun activityLoaded (Landroid/app/Activity;)V public fun addBreadcrumb (Ljava/lang/String;)V public fun addLogRecordExporter (Lio/opentelemetry/sdk/logs/export/LogRecordExporter;)V public fun addSessionProperty (Ljava/lang/String;Ljava/lang/String;Z)Z diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/UiLoadTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/UiLoadTest.kt index a7ac088f2b..e76204c21d 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/UiLoadTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/features/UiLoadTest.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.testcases.features import android.app.Activity import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.annotation.CustomTracedActivity import io.embrace.android.embracesdk.annotation.NotTracedActivity import io.embrace.android.embracesdk.annotation.TracedActivity import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData @@ -13,6 +14,8 @@ import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.capture.activity.LifecycleStage import io.embrace.android.embracesdk.internal.payload.ApplicationState +import io.embrace.android.embracesdk.internal.payload.Span +import io.embrace.android.embracesdk.spans.ErrorCode import io.embrace.android.embracesdk.testframework.IntegrationTestRule import io.opentelemetry.api.trace.SpanId import org.junit.Assert.assertEquals @@ -77,6 +80,87 @@ internal class UiLoadTest { ) } + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `activity open creates a trace for manually ended load`() { + var preLaunchTimeMs = 0L + testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig( + enabledFeatures = FakeEnabledFeatureConfig( + uiLoadPerfCapture = true, + uiLoadPerfAutoCapture = false, + bgActivityCapture = true + ) + ), + setupAction = { + preLaunchTimeMs = overriddenClock.now() + }, + testCaseAction = { + simulateOpeningActivities( + invokeManualEnd = true, + activitiesAndActions = listOf( + Robolectric.buildActivity(ManualStopActivity::class.java) to {}, + ) + ) + }, + assertAction = { + with(getSingleSessionEnvelope()) { + val trace = findSpansOfType(EmbType.Performance.UiLoad).single() + assertEquals("emb-$MANUAL_STOP_ACTIVITY_NAME-cold-time-to-initial-display", trace.name) + + val expectedTraceStartTime = preLaunchTimeMs + 50 + assertEmbraceSpanData( + span = trace, + expectedStartTimeMs = expectedTraceStartTime, + expectedEndTimeMs = expectedTraceStartTime + 400, + expectedParentId = SpanId.getInvalid(), + ) + } + } + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `opening activity configured to be ended manually creates an abandoned trace if activityLoaded not invoked`() { + var preLaunchTimeMs = 0L + testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig( + enabledFeatures = FakeEnabledFeatureConfig( + uiLoadPerfCapture = true, + uiLoadPerfAutoCapture = false, + bgActivityCapture = true + ) + ), + setupAction = { + preLaunchTimeMs = overriddenClock.now() + }, + testCaseAction = { + simulateOpeningActivities( + activitiesAndActions = listOf( + Robolectric.buildActivity(ManualStopActivity::class.java) to {}, + ) + ) + }, + assertAction = { + with(getSingleSessionEnvelope()) { + val trace = findSpansOfType(EmbType.Performance.UiLoad).single() + assertEquals("emb-$MANUAL_STOP_ACTIVITY_NAME-cold-time-to-initial-display", trace.name) + + val expectedTraceStartTime = preLaunchTimeMs + 50 + assertEmbraceSpanData( + span = trace, + expectedStartTimeMs = expectedTraceStartTime, + expectedEndTimeMs = expectedTraceStartTime + 20301, + expectedParentId = SpanId.getInvalid(), + expectedStatus = Span.Status.ERROR, + expectedErrorCode = ErrorCode.USER_ABANDON, + ) + } + } + ) + } + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) @Test fun `activity open does not create a trace for explicitly disabled activity even if auto capture enabled`() { @@ -263,7 +347,11 @@ internal class UiLoadTest { @NotTracedActivity class IgnoredActivity : Activity() + @CustomTracedActivity + class ManualStopActivity : Activity() + val ACTIVITY1_NAME = Robolectric.buildActivity(Activity1::class.java).get().localClassName val ACTIVITY2_NAME = Robolectric.buildActivity(Activity2::class.java).get().localClassName + val MANUAL_STOP_ACTIVITY_NAME = Robolectric.buildActivity(ManualStopActivity::class.java).get().localClassName } } diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt index 7f391e45e4..5fcb561640 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testframework/actions/EmbraceActionInterface.kt @@ -64,6 +64,7 @@ internal class EmbraceActionInterface( addStartupActivity: Boolean = true, startInBackground: Boolean = false, createFirstActivity: Boolean = true, + invokeManualEnd: Boolean = false, activitiesAndActions: List, () -> Unit>> = listOf(), lifecycleEventGap: Long = 100L, postActionDwell: Long = 20000L, @@ -99,6 +100,10 @@ internal class EmbraceActionInterface( } activityController.resume() setup.overriddenClock.tick(lifecycleEventGap) + if (invokeManualEnd) { + setup.overriddenClock.tick(lifecycleEventGap) + embrace.activityLoaded(activityController.get()) + } lastActivity?.stop() setup.overriddenClock.tick() diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.kt index 5fdfbe2754..64dc0afca6 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk import android.annotation.SuppressLint +import android.app.Activity import android.content.Context import android.webkit.ConsoleMessage import io.embrace.android.embracesdk.internal.Systrace @@ -422,4 +423,8 @@ public class Embrace private constructor( override fun disable() { impl.disable() } + + override fun activityLoaded(activity: Activity) { + impl.activityLoaded(activity) + } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt index 29a91651c6..0ca46f7a5d 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.kt @@ -15,6 +15,7 @@ import io.embrace.android.embracesdk.internal.Systrace.startSynchronous import io.embrace.android.embracesdk.internal.UnityInternalInterface import io.embrace.android.embracesdk.internal.anr.ndk.isUnityMainThread import io.embrace.android.embracesdk.internal.api.BreadcrumbApi +import io.embrace.android.embracesdk.internal.api.InstrumentationApi import io.embrace.android.embracesdk.internal.api.InternalWebViewApi import io.embrace.android.embracesdk.internal.api.LogsApi import io.embrace.android.embracesdk.internal.api.NetworkRequestApi @@ -25,6 +26,7 @@ import io.embrace.android.embracesdk.internal.api.SessionApi import io.embrace.android.embracesdk.internal.api.UserApi import io.embrace.android.embracesdk.internal.api.ViewTrackingApi import io.embrace.android.embracesdk.internal.api.delegate.BreadcrumbApiDelegate +import io.embrace.android.embracesdk.internal.api.delegate.InstrumentationApiDelegate import io.embrace.android.embracesdk.internal.api.delegate.InternalWebViewApiDelegate import io.embrace.android.embracesdk.internal.api.delegate.LogsApiDelegate import io.embrace.android.embracesdk.internal.api.delegate.NetworkRequestApiDelegate @@ -76,6 +78,8 @@ internal class EmbraceImpl @JvmOverloads constructor( private val breadcrumbApiDelegate: BreadcrumbApiDelegate = BreadcrumbApiDelegate(bootstrapper, sdkCallChecker), private val webviewApiDelegate: InternalWebViewApiDelegate = InternalWebViewApiDelegate(bootstrapper, sdkCallChecker), + private val instrumentationApiDelegate: InstrumentationApiDelegate = + InstrumentationApiDelegate(bootstrapper, sdkCallChecker) ) : SdkApi, LogsApi by logsApiDelegate, NetworkRequestApi by networkRequestApiDelegate, @@ -87,6 +91,7 @@ internal class EmbraceImpl @JvmOverloads constructor( ViewTrackingApi by viewTrackingApiDelegate, BreadcrumbApi by breadcrumbApiDelegate, InternalWebViewApi by webviewApiDelegate, + InstrumentationApi by instrumentationApiDelegate, InternalInterfaceApi { init { diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/InstrumentationApiDelegate.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/InstrumentationApiDelegate.kt new file mode 100644 index 0000000000..4f6fab0265 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/api/delegate/InstrumentationApiDelegate.kt @@ -0,0 +1,25 @@ +package io.embrace.android.embracesdk.internal.api.delegate + +import android.app.Activity +import io.embrace.android.embracesdk.internal.api.InstrumentationApi +import io.embrace.android.embracesdk.internal.capture.activity.traceInstanceId +import io.embrace.android.embracesdk.internal.clock.Clock +import io.embrace.android.embracesdk.internal.injection.ModuleInitBootstrapper +import io.embrace.android.embracesdk.internal.injection.embraceImplInject + +internal class InstrumentationApiDelegate( + bootstrapper: ModuleInitBootstrapper, + private val sdkCallChecker: SdkCallChecker, +) : InstrumentationApi { + + private val clock: Clock = bootstrapper.initModule.clock + private val uiLoadTraceEmitter by embraceImplInject(sdkCallChecker) { + bootstrapper.dataCaptureServiceModule.uiLoadTraceEmitter + } + + override fun activityLoaded(activity: Activity) { + if (sdkCallChecker.check("activity_fully_loaded")) { + uiLoadTraceEmitter?.complete(traceInstanceId(activity), clock.now()) + } + } +} diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeCustomTracedActivity.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeCustomTracedActivity.kt new file mode 100644 index 0000000000..cddcd2fc1a --- /dev/null +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeCustomTracedActivity.kt @@ -0,0 +1,7 @@ +package io.embrace.android.embracesdk.fakes + +import android.app.Activity +import io.embrace.android.embracesdk.annotation.CustomTracedActivity + +@CustomTracedActivity +class FakeCustomTracedActivity : Activity()