diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt index f443f1ab97..22eccb4ad4 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImpl.kt @@ -17,6 +17,7 @@ class AutoDataCaptureBehaviorImpl( const val THERMAL_STATUS_ENABLED_DEFAULT = true const val V2_STORAGE_ENABLED_DEFAULT = true const val USE_OKHTTP_DEFAULT = true + const val UI_LOAD_REMOTE_ENABLED_DEFAULT = false } override val local = local.enabledFeatures @@ -43,8 +44,11 @@ class AutoDataCaptureBehaviorImpl( override fun isNativeCrashCaptureEnabled(): Boolean = local.isNativeCrashCaptureEnabled() override fun isDiskUsageCaptureEnabled(): Boolean = local.isDiskUsageCaptureEnabled() - override fun isUiLoadTracingEnabled(): Boolean = local.isUiLoadTracingEnabled() - override fun isUiLoadTracingTraceAll(): Boolean = local.isUiLoadTracingTraceAll() + override fun isUiLoadTracingEnabled(): Boolean = local.isUiLoadTracingEnabled() && uiLoadEnabledRemotely() + override fun isUiLoadTracingTraceAll(): Boolean = local.isUiLoadTracingTraceAll() && uiLoadEnabledRemotely() + + private fun uiLoadEnabledRemotely(): Boolean = + remote?.uiLoadInstrumentationEnabled ?: UI_LOAD_REMOTE_ENABLED_DEFAULT private val v2StorageImpl by lazy { thresholdCheck.isBehaviorEnabled(remote?.killSwitchConfig?.v2StoragePct) ?: V2_STORAGE_ENABLED_DEFAULT diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt index a53b41dcd8..634eb72bd1 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/config/behavior/AutoDataCaptureBehaviorImplTest.kt @@ -1,9 +1,12 @@ package io.embrace.android.embracesdk.internal.config.behavior +import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig +import io.embrace.android.embracesdk.fakes.config.FakeInstrumentedConfig import io.embrace.android.embracesdk.fakes.createAutoDataCaptureBehavior import io.embrace.android.embracesdk.internal.config.remote.DataRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.KillSwitchRemoteConfig import io.embrace.android.embracesdk.internal.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.utils.Uuid import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -17,7 +20,8 @@ internal class AutoDataCaptureBehaviorImplTest { v2StoragePct = 100f, useOkHttpPct = 100f ), - dataConfig = DataRemoteConfig(pctThermalStatusEnabled = 0.0f) + dataConfig = DataRemoteConfig(pctThermalStatusEnabled = 0.0f), + uiLoadInstrumentationEnabled = false, ) @Test @@ -33,8 +37,8 @@ internal class AutoDataCaptureBehaviorImplTest { assertFalse(isNativeCrashCaptureEnabled()) assertTrue(isDiskUsageCaptureEnabled()) assertTrue(isThermalStatusCaptureEnabled()) - assertTrue(isUiLoadTracingEnabled()) - assertTrue(isUiLoadTracingTraceAll()) + assertFalse(isUiLoadTracingEnabled()) + assertFalse(isUiLoadTracingTraceAll()) assertTrue(isThermalStatusCaptureEnabled()) assertTrue(isV2StorageEnabled()) } @@ -77,4 +81,55 @@ internal class AutoDataCaptureBehaviorImplTest { assertTrue(isComposeClickCaptureEnabled()) } } + + @Test + fun `disable ui load remotely`() { + val behavior = createBehavior( + localUiLoadTracingEnabled = true, + localUiLoadTracingTraceAllEnabled = true, + remote = remote.copy(uiLoadInstrumentationEnabled = false) + ) + + assertFalse(behavior.isUiLoadTracingEnabled()) + assertFalse(behavior.isUiLoadTracingTraceAll()) + } + + @Test + fun `disable ui load locally`() { + val behavior = createBehavior( + localUiLoadTracingEnabled = false, + localUiLoadTracingTraceAllEnabled = false, + remote = remote.copy(uiLoadInstrumentationEnabled = true) + ) + + assertFalse(behavior.isUiLoadTracingEnabled()) + assertFalse(behavior.isUiLoadTracingTraceAll()) + } + + @Test + fun `disable ui load trace all locally`() { + val behavior = createBehavior( + localUiLoadTracingEnabled = true, + localUiLoadTracingTraceAllEnabled = false, + remote = remote.copy(uiLoadInstrumentationEnabled = true) + ) + + assertTrue(behavior.isUiLoadTracingEnabled()) + assertFalse(behavior.isUiLoadTracingTraceAll()) + } + + private fun createBehavior( + localUiLoadTracingEnabled: Boolean, + localUiLoadTracingTraceAllEnabled: Boolean, + remote: RemoteConfig, + ) = AutoDataCaptureBehaviorImpl( + thresholdCheck = BehaviorThresholdCheck(Uuid::getEmbUuid), + local = FakeInstrumentedConfig( + enabledFeatures = FakeEnabledFeatureConfig( + uiLoadTracingTraceAll = localUiLoadTracingTraceAllEnabled, + uiLoadTracingEnabled = localUiLoadTracingEnabled + ) + ), + remote = remote + ) } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImplTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImplTest.kt index c5c73a398c..e06a071b82 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImplTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImplTest.kt @@ -31,8 +31,8 @@ internal class DataCaptureServiceModuleImplTest { assertNotNull(module.appStartupDataCollector) assertNotNull(module.pushNotificationService) assertNotNull(module.startupService) - assertNotNull(module.activityLoadEventEmitter) - assertNotNull(module.uiLoadDataListener) + assertNull(module.activityLoadEventEmitter) + assertNull(module.uiLoadDataListener) } @Test diff --git a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/remote/RemoteConfig.kt b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/remote/RemoteConfig.kt index de6349a5e2..bef6565f1d 100644 --- a/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/remote/RemoteConfig.kt +++ b/embrace-android-payload/src/main/kotlin/io/embrace/android/embracesdk/internal/config/remote/RemoteConfig.kt @@ -94,4 +94,7 @@ data class RemoteConfig( */ @Json(name = "webview_vitals_beta") val webViewVitals: WebViewVitals? = null, + + @Json(name = "ui_load_instrumentation_enabled") + val uiLoadInstrumentationEnabled: Boolean? = null, ) 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 2e28a85c58..9245889642 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 @@ -13,6 +13,7 @@ import io.embrace.android.embracesdk.fakes.config.FakeEnabledFeatureConfig 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.config.remote.RemoteConfig import io.embrace.android.embracesdk.internal.payload.ApplicationState import io.embrace.android.embracesdk.internal.payload.Span import io.embrace.android.embracesdk.spans.ErrorCode @@ -39,11 +40,29 @@ internal class UiLoadTest { @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) @Test - fun `activity open creates a trace by default`() { + fun `activity open does not create a trace by default`() { testRule.runTest( instrumentedConfig = FakeInstrumentedConfig( - enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true) + enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true), ), + persistedRemoteConfig = RemoteConfig(), + testCaseAction = { + simulateOpeningActivities() + }, + assertAction = { + assertTrue(getSingleSessionEnvelope().findSpansOfType(EmbType.Performance.UiLoad).isEmpty()) + } + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `activity open creates a trace by default if remote config is enabled`() { + testRule.runTest( + instrumentedConfig = FakeInstrumentedConfig( + enabledFeatures = FakeEnabledFeatureConfig(bgActivityCapture = true), + ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), testCaseAction = { simulateOpeningActivities() }, @@ -60,6 +79,7 @@ internal class UiLoadTest { instrumentedConfig = FakeInstrumentedConfig( enabledFeatures = FakeEnabledFeatureConfig(uiLoadTracingEnabled = false, bgActivityCapture = true) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), testCaseAction = { simulateOpeningActivities() }, @@ -80,6 +100,7 @@ internal class UiLoadTest { bgActivityCapture = true ) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), testCaseAction = { simulateOpeningActivities() }, @@ -101,6 +122,7 @@ internal class UiLoadTest { bgActivityCapture = true ) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), setupAction = { preLaunchTimeMs = overriddenClock.now() }, @@ -150,6 +172,7 @@ internal class UiLoadTest { bgActivityCapture = true ) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), setupAction = { preLaunchTimeMs = overriddenClock.now() }, @@ -169,7 +192,10 @@ internal class UiLoadTest { assertEmbraceSpanData( span = trace, expectedStartTimeMs = expectedTraceStartTime, - expectedEndTimeMs = expectedTraceStartTime + calculateTotalTime(lifecycleStages = 3, activitiesFullyLoaded = 1), + expectedEndTimeMs = expectedTraceStartTime + calculateTotalTime( + lifecycleStages = 3, + activitiesFullyLoaded = 1 + ), expectedParentId = SpanId.getInvalid(), expectedStatus = Span.Status.ERROR, expectedErrorCode = ErrorCode.USER_ABANDON, @@ -190,6 +216,7 @@ internal class UiLoadTest { bgActivityCapture = true ) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), testCaseAction = { simulateOpeningActivities( activitiesAndActions = listOf( @@ -211,6 +238,7 @@ internal class UiLoadTest { instrumentedConfig = FakeInstrumentedConfig( enabledFeatures = FakeEnabledFeatureConfig(uiLoadTracingEnabled = true, bgActivityCapture = true) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), setupAction = { preLaunchTimeMs = overriddenClock.now() }, @@ -265,6 +293,7 @@ internal class UiLoadTest { instrumentedConfig = FakeInstrumentedConfig( enabledFeatures = FakeEnabledFeatureConfig(uiLoadTracingEnabled = true, bgActivityCapture = true) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), setupAction = { preLaunchTimeMs = overriddenClock.now() }, @@ -320,6 +349,7 @@ internal class UiLoadTest { instrumentedConfig = FakeInstrumentedConfig( enabledFeatures = FakeEnabledFeatureConfig(uiLoadTracingEnabled = true, bgActivityCapture = true) ), + persistedRemoteConfig = RemoteConfig(uiLoadInstrumentationEnabled = true), setupAction = { preLaunchTimeMs = overriddenClock.now() },