diff --git a/detekt_custom.yml b/detekt_custom.yml index 7ea0ea25bc..86d49ad720 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -717,6 +717,7 @@ datadog: - "java.lang.ref.WeakReference.constructor(android.content.Context?)" - "java.lang.ref.WeakReference.constructor(android.view.View?)" - "java.lang.ref.WeakReference.constructor(android.view.Window?)" + - "java.lang.ref.WeakReference.constructor(com.datadog.android.api.SdkCore?)" - "java.lang.ref.WeakReference.constructor(kotlin.Any?)" - "java.lang.ref.WeakReference.constructor(kotlin.Nothing?)" - "java.lang.ref.WeakReference.constructor(kotlin.String?)" diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index 1840ab7b47..8a7c3b86e5 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -6,17 +6,27 @@ package com.datadog.android.sessionreplay +import androidx.annotation.VisibleForTesting import com.datadog.android.Datadog +import com.datadog.android.api.InternalLogger import com.datadog.android.api.SdkCore import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.internal.SessionReplayFeature +import java.lang.ref.WeakReference /** * An entry point to Datadog Session Replay feature. */ object SessionReplay { + // core on which session replay was enabled + @VisibleForTesting internal var sdkCoreEnabledOn: WeakReference? = null + + internal const val FEATURE_ALREADY_REGISTERED_WARNING = + "Session Replay is already enabled and does not support multiple instances. " + + "The existing instance will continue to be used." + /** * Enables a SessionReplay feature based on the configuration provided. * It is recommended to invoke this function as early as possible in the app's lifecycle, @@ -48,7 +58,18 @@ object SessionReplay { startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately, dynamicOptimizationEnabled = sessionReplayConfiguration.dynamicOptimizationEnabled ) - sdkCore.registerFeature(sessionReplayFeature) + + val prevRecordedCore = sdkCoreEnabledOn?.get() + if (prevRecordedCore == null) { + sdkCoreEnabledOn = WeakReference(sdkCore) + sdkCore.registerFeature(sessionReplayFeature) + } else { + sdkCore.internalLogger.log( + level = InternalLogger.Level.WARN, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + messageBuilder = { FEATURE_ALREADY_REGISTERED_WARNING } + ) + } } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt index be8e240aa5..063d5ed70a 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt @@ -6,12 +6,15 @@ package com.datadog.android.sessionreplay +import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore +import com.datadog.android.sessionreplay.SessionReplay.FEATURE_ALREADY_REGISTERED_WARNING import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.SessionReplayFeature import com.datadog.android.sessionreplay.internal.net.SegmentRequestFactory +import com.datadog.android.sessionreplay.utils.verifyLog import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -50,6 +53,7 @@ internal class SessionReplayTest { @BeforeEach fun `set up`() { whenever(mockSdkCore.internalLogger) doReturn mock() + SessionReplay.sdkCoreEnabledOn = null } @Test @@ -119,4 +123,41 @@ internal class SessionReplayTest { // Then verify(mockSessionReplayFeature).manuallyStopRecording() } + + @Test + fun `M warn and send telemetry W enable { session replay feature already registered with another core }`( + @Forgery fakeSessionReplayConfiguration: SessionReplayConfiguration, + @Mock mockCore1: FeatureSdkCore, + @Mock mockCore2: FeatureSdkCore, + @Mock mockInternalLogger: InternalLogger + ) { + // Given + whenever(mockCore1.internalLogger).thenReturn(mockInternalLogger) + whenever(mockCore2.internalLogger).thenReturn(mockInternalLogger) + val fakeSessionReplayConfigurationWithMockRequirement = fakeSessionReplayConfiguration.copy( + systemRequirementsConfiguration = mockSystemRequirementsConfiguration + ) + whenever( + mockSystemRequirementsConfiguration.runIfRequirementsMet(any(), any()) + ) doAnswer { + it.getArgument<() -> Unit>(1).invoke() + } + SessionReplay.enable( + sessionReplayConfiguration = fakeSessionReplayConfigurationWithMockRequirement, + sdkCore = mockCore1 + ) + + // When + SessionReplay.enable( + sessionReplayConfiguration = fakeSessionReplayConfigurationWithMockRequirement, + sdkCore = mockCore2 + ) + + // Then + mockInternalLogger.verifyLog( + level = InternalLogger.Level.WARN, + targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY), + message = FEATURE_ALREADY_REGISTERED_WARNING + ) + } }