From a72e71f977d1d4256345cedade735d72cd6bad95 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Tue, 10 Sep 2024 15:06:00 -0700 Subject: [PATCH] Create callback mechanism to run code post app startup --- .../startup/AppStartupDataCollector.kt | 4 +-- .../capture/startup/AppStartupTraceEmitter.kt | 19 ++++++++++---- .../capture/startup/StartupTracker.kt | 12 +++++++-- .../startup/AppStartupTraceEmitterTest.kt | 26 ++++++++++++++----- .../fakes/FakeAppStartupDataCollector.kt | 12 +++++++-- 5 files changed, 55 insertions(+), 18 deletions(-) diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt index 3e4231e222..8993541f27 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt @@ -40,12 +40,12 @@ interface AppStartupDataCollector { /** * Set the time for when the startup Activity begins to render as well as its name */ - fun startupActivityResumed(activityName: String, timestampMs: Long? = null) + fun startupActivityResumed(activityName: String, collectionCompleteCallback: () -> Unit, timestampMs: Long? = null) /** * Set the time for when the startup Activity has finished rendering its first frame as well as its name */ - fun firstFrameRendered(activityName: String, timestampMs: Long? = null) + fun firstFrameRendered(activityName: String, collectionCompleteCallback: () -> Unit, timestampMs: Long? = null) /** * Set an arbitrary time interval during startup that is of note diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt index 13bfdf6ab1..ab6064b874 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt @@ -127,19 +127,27 @@ internal class AppStartupTraceEmitter( startupActivityInitEndMs = timestampMs ?: nowMs() } - override fun startupActivityResumed(activityName: String, timestampMs: Long?) { + override fun startupActivityResumed( + activityName: String, + collectionCompleteCallback: () -> Unit, + timestampMs: Long? + ) { startupActivityName = activityName startupActivityResumedMs = timestampMs ?: nowMs() if (!endWithFrameDraw) { - dataCollectionComplete() + dataCollectionComplete(collectionCompleteCallback) } } - override fun firstFrameRendered(activityName: String, timestampMs: Long?) { + override fun firstFrameRendered( + activityName: String, + collectionCompleteCallback: () -> Unit, + timestampMs: Long? + ) { startupActivityName = activityName firstFrameRenderedMs = timestampMs ?: nowMs() if (endWithFrameDraw) { - dataCollectionComplete() + dataCollectionComplete(collectionCompleteCallback) } } @@ -152,7 +160,7 @@ internal class AppStartupTraceEmitter( /** * Called when app startup is considered complete, i.e. the data can be used and any additional updates can be ignored */ - private fun dataCollectionComplete() { + private fun dataCollectionComplete(callback: () -> Unit) { if (!startupRecorded.get()) { synchronized(startupRecorded) { if (!startupRecorded.get()) { @@ -162,6 +170,7 @@ internal class AppStartupTraceEmitter( logger.logWarning("App startup trace recording attempted but did not succeed") } } + callback() } } } diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt index ceac82f7fc..37d0d9446f 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt @@ -63,7 +63,12 @@ class StartupTracker( decorView.onNextDraw { if (!isFirstDraw) { isFirstDraw = true - val callback = { appStartupDataCollector.firstFrameRendered(activityName = activityName) } + val callback = { + appStartupDataCollector.firstFrameRendered( + activityName = activityName, + collectionCompleteCallback = {} + ) + } decorView.viewTreeObserver.registerFrameCommitCallback(callback) } } @@ -91,7 +96,10 @@ class StartupTracker( override fun onActivityResumed(activity: Activity) { if (activity.observeForStartup()) { - appStartupDataCollector.startupActivityResumed(activityName = activity.localClassName) + appStartupDataCollector.startupActivityResumed( + activityName = activity.localClassName, + collectionCompleteCallback = { } + ) } } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitterTest.kt index ee563f0813..346ee239e9 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitterTest.kt @@ -21,6 +21,7 @@ import io.embrace.android.embracesdk.internal.spans.findAttributeValue import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker import io.embrace.android.embracesdk.internal.worker.BackgroundWorker import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -37,6 +38,7 @@ import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) internal class AppStartupTraceEmitterTest { private var startupService: StartupService? = null + private var dataCollectionCompletedCallbackInvoked: Boolean = false private lateinit var clock: FakeClock private lateinit var spanSink: SpanSink private lateinit var spanService: SpanService @@ -75,7 +77,8 @@ internal class AppStartupTraceEmitterTest { @Test fun `no crashes if startup service not available in T`() { startupService = null - appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) + assertTrue(dataCollectionCompletedCallbackInvoked) } @Config(sdk = [Build.VERSION_CODES.TIRAMISU]) @@ -106,7 +109,8 @@ internal class AppStartupTraceEmitterTest { @Test fun `no crashes if startup service not available in S`() { startupService = null - appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) + assertTrue(dataCollectionCompletedCallbackInvoked) } @Config(sdk = [Build.VERSION_CODES.S]) @@ -137,7 +141,8 @@ internal class AppStartupTraceEmitterTest { @Test fun `no crashes if startup service not available in P`() { startupService = null - appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) + assertTrue(dataCollectionCompletedCallbackInvoked) } @Config(sdk = [Build.VERSION_CODES.P]) @@ -162,7 +167,8 @@ internal class AppStartupTraceEmitterTest { @Test fun `no crashes if startup service not available in M`() { startupService = null - appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) + assertTrue(dataCollectionCompletedCallbackInvoked) } @Config(sdk = [Build.VERSION_CODES.M]) @@ -187,7 +193,8 @@ internal class AppStartupTraceEmitterTest { @Test fun `no crashes if startup service not available in L`() { startupService = null - appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) + assertTrue(dataCollectionCompletedCallbackInvoked) } @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) @@ -208,6 +215,10 @@ internal class AppStartupTraceEmitterTest { verifyWarmStartWithResumeWithoutAppInitEvents() } + private fun dataCollectionCompletedCallback() { + dataCollectionCompletedCallbackInvoked = true + } + private fun verifyColdStartWithRender(processCreateDelayMs: Long? = null) { clock.tick(100L) appStartupTraceEmitter.applicationInitStart() @@ -518,11 +529,11 @@ internal class AppStartupTraceEmitterTest { } private fun startupActivityRender(renderFrame: Boolean = true): Pair { - appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) val resumed = clock.now() if (renderFrame) { clock.tick(199L) - appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME) + appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback) } return Pair(resumed, clock.now()) } @@ -549,6 +560,7 @@ internal class AppStartupTraceEmitterTest { assertEquals(expectedFirstActivityLifecycleEventMs?.toString(), attrs.findAttributeValue("first-activity-init-ms")) assertEquals("false", attrs.findAttributeValue("embrace-init-in-foreground")) assertEquals("main", attrs.findAttributeValue("embrace-init-thread-name")) + assertTrue(dataCollectionCompletedCallbackInvoked) } private fun assertChildSpan(span: EmbraceSpanData, expectedStartTimeNanos: Long, expectedEndTimeNanos: Long) { diff --git a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeAppStartupDataCollector.kt b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeAppStartupDataCollector.kt index c17fccbda6..e65ce33775 100644 --- a/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeAppStartupDataCollector.kt +++ b/embrace-test-fakes/src/main/kotlin/io/embrace/android/embracesdk/fakes/FakeAppStartupDataCollector.kt @@ -39,12 +39,20 @@ class FakeAppStartupDataCollector( startupActivityInitEndMs = timestampMs ?: clock.now() } - override fun startupActivityResumed(activityName: String, timestampMs: Long?) { + override fun startupActivityResumed( + activityName: String, + collectionCompleteCallback: () -> Unit, + timestampMs: Long? + ) { startupActivityName = activityName startupActivityResumedMs = timestampMs ?: clock.now() } - override fun firstFrameRendered(activityName: String, timestampMs: Long?) { + override fun firstFrameRendered( + activityName: String, + collectionCompleteCallback: () -> Unit, + timestampMs: Long? + ) { startupActivityName = activityName firstFrameRenderedMs = timestampMs ?: clock.now() }