Skip to content

Commit fbf6809

Browse files
committed
PANA-8500: Add composition pipeline selection
1 parent 811d857 commit fbf6809

14 files changed

Lines changed: 377 additions & 29 deletions

features/dd-sdk-android-session-replay/api/apiSurface

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ enum com.datadog.android.sessionreplay.TouchPrivacy : PrivacyLevel
7777
class com.datadog.android.sessionreplay._SessionReplayInternalProxy
7878
constructor(SessionReplayConfiguration.Builder)
7979
fun setInternalCallback(SessionReplayInternalCallback): SessionReplayConfiguration.Builder
80+
fun setCompositionTreeRecordingEnabled(Boolean): SessionReplayConfiguration.Builder
8081
class com.datadog.android.sessionreplay.internal.TouchPrivacyManager
8182
constructor(com.datadog.android.sessionreplay.TouchPrivacy)
8283
fun addTouchOverrideArea(android.graphics.Rect, com.datadog.android.sessionreplay.TouchPrivacy)

features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ public final class com/datadog/android/sessionreplay/SessionReplay {
6464
}
6565

6666
public final class com/datadog/android/sessionreplay/SessionReplayConfiguration {
67-
public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ZLcom/datadog/android/sessionreplay/SystemRequirementsConfiguration;Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;
68-
public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ZLcom/datadog/android/sessionreplay/SystemRequirementsConfiguration;Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;
67+
public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ZLcom/datadog/android/sessionreplay/SystemRequirementsConfiguration;Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;ZZ)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;
68+
public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ZLcom/datadog/android/sessionreplay/SystemRequirementsConfiguration;Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;ZZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;
6969
public fun equals (Ljava/lang/Object;)Z
7070
public fun hashCode ()I
7171
public fun toString ()Ljava/lang/String;
@@ -147,6 +147,7 @@ public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/En
147147

148148
public final class com/datadog/android/sessionreplay/_SessionReplayInternalProxy {
149149
public fun <init> (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;)V
150+
public final fun setCompositionTreeRecordingEnabled (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
150151
public final fun setInternalCallback (Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
151152
}
152153

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ object SessionReplay {
6161
startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately,
6262
dynamicOptimizationEnabled = sessionReplayConfiguration.dynamicOptimizationEnabled,
6363
internalCallback = sessionReplayConfiguration.internalCallback,
64-
heatmapsEnabled = sessionReplayConfiguration.heatmapsEnabled
64+
heatmapsEnabled = sessionReplayConfiguration.heatmapsEnabled,
65+
compositionTreeRecordingEnabled = sessionReplayConfiguration.compositionTreeRecordingEnabled
6566
)
6667

6768
if (isAlreadyRegistered()) {

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package com.datadog.android.sessionreplay
88

9+
import android.view.View
910
import androidx.annotation.FloatRange
1011
import com.datadog.android.api.InternalLogger
1112
import com.datadog.android.sessionreplay.internal.recorder.SessionReplayRecorder
@@ -30,7 +31,8 @@ data class SessionReplayConfiguration internal constructor(
3031
internal val dynamicOptimizationEnabled: Boolean,
3132
internal val systemRequirementsConfiguration: SystemRequirementsConfiguration,
3233
internal val internalCallback: SessionReplayInternalCallback,
33-
internal val heatmapsEnabled: Boolean
34+
internal val heatmapsEnabled: Boolean,
35+
internal val compositionTreeRecordingEnabled: Boolean
3436
) {
3537

3638
/**
@@ -78,6 +80,7 @@ data class SessionReplayConfiguration internal constructor(
7880
private var systemRequirementsConfiguration = SystemRequirementsConfiguration.NONE
7981
private var internalCallback: SessionReplayInternalCallback = NoOpSessionReplayInternalCallback()
8082
private var heatmapsEnabled = false
83+
private var compositionTreeRecordingEnabled = false
8184

8285
/**
8386
* Adds an extension support implementation. This is mostly used when you want to provide
@@ -239,6 +242,27 @@ data class SessionReplayConfiguration internal constructor(
239242
return this
240243
}
241244

245+
/**
246+
* Enables the experimental composition-tree recording pipeline.
247+
*
248+
* The default recorder builds replay wireframes from semantic information extracted from
249+
* Android [View] properties. This pipeline uses the rendered composition tree as its primary
250+
* source, preserving its structure and rendering effects to improve replay fidelity in
251+
* Android View and Jetpack Compose applications.
252+
*
253+
* The underlying capture pipeline is not implemented yet: enabling this flag currently
254+
* disables Session Replay recording entirely instead of falling back to the default
255+
* recorder. Kept internal until the pipeline is functional; see [_SessionReplayInternalProxy].
256+
*
257+
* Disabled by default.
258+
*
259+
* @param enabled whether composition-tree recording should be used.
260+
*/
261+
internal fun setCompositionTreeRecordingEnabled(enabled: Boolean): Builder {
262+
compositionTreeRecordingEnabled = enabled
263+
return this
264+
}
265+
242266
/**
243267
* Builds a [SessionReplayConfiguration] based on the current state of this Builder.
244268
*/
@@ -257,7 +281,8 @@ data class SessionReplayConfiguration internal constructor(
257281
dynamicOptimizationEnabled = dynamicOptimizationEnabled,
258282
systemRequirementsConfiguration = systemRequirementsConfiguration,
259283
internalCallback = internalCallback,
260-
heatmapsEnabled = heatmapsEnabled
284+
heatmapsEnabled = heatmapsEnabled,
285+
compositionTreeRecordingEnabled = compositionTreeRecordingEnabled
261286
)
262287
}
263288

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/_SessionReplayInternalProxy.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,17 @@ class _SessionReplayInternalProxy(private val builder: SessionReplayConfiguratio
3535
): SessionReplayConfiguration.Builder {
3636
return builder.setInternalCallback(internalCallback)
3737
}
38+
39+
/**
40+
* Enables the experimental composition-tree recording pipeline.
41+
*
42+
* The underlying capture pipeline is not implemented yet: enabling this flag currently
43+
* disables Session Replay recording entirely instead of falling back to the default recorder.
44+
*
45+
* @param enabled whether composition-tree recording should be used.
46+
* @return [SessionReplayConfiguration.Builder] instance.
47+
*/
48+
fun setCompositionTreeRecordingEnabled(enabled: Boolean): SessionReplayConfiguration.Builder {
49+
return builder.setCompositionTreeRecordingEnabled(enabled)
50+
}
3851
}

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import com.datadog.android.sessionreplay.ImagePrivacy
2727
import com.datadog.android.sessionreplay.MapperTypeWrapper
2828
import com.datadog.android.sessionreplay.SessionReplayInternalCallback
2929
import com.datadog.android.sessionreplay.TextAndInputPrivacy
30+
import com.datadog.android.sessionreplay.internal.composition.CapturePipelineSelector
31+
import com.datadog.android.sessionreplay.internal.composition.CompositionCapturePipeline
3032
import com.datadog.android.sessionreplay.internal.recorder.Recorder
3133
import com.datadog.android.sessionreplay.internal.recorder.SessionReplayRecorder
3234
import com.datadog.android.sessionreplay.internal.recorder.mapper.ActionBarContainerMapper
@@ -67,7 +69,9 @@ internal class DefaultRecorderProvider(
6769
private val customDrawableMappers: List<DrawableToColorMapper>,
6870
private val dynamicOptimizationEnabled: Boolean,
6971
private val internalCallback: SessionReplayInternalCallback,
70-
private val heatmapsEnabled: Boolean
72+
private val heatmapsEnabled: Boolean,
73+
private val compositionTreeRecordingEnabled: Boolean,
74+
private val compositionPipelineFactory: () -> Recorder = { CompositionCapturePipeline() }
7175
) : RecorderProvider {
7276

7377
override fun provideSessionReplayRecorder(
@@ -77,24 +81,34 @@ internal class DefaultRecorderProvider(
7781
rumContextProvider: RumContextProvider,
7882
application: Application
7983
): Recorder {
80-
return SessionReplayRecorder(
81-
application,
82-
resourceDataStoreManager = resourceDataStoreManager,
83-
resourcesWriter = resourceWriter,
84-
rumContextProvider = rumContextProvider,
85-
imagePrivacy = imagePrivacy,
86-
touchPrivacyManager = touchPrivacyManager,
87-
textAndInputPrivacy = textAndInputPrivacy,
88-
recordWriter = recordWriter,
89-
timeProvider = sdkCore.timeProvider,
90-
mappers = customMappers + builtInMappers(),
91-
customOptionSelectorDetectors = customOptionSelectorDetectors,
92-
customDrawableMappers = customDrawableMappers,
93-
sdkCore = sdkCore,
94-
dynamicOptimizationEnabled = dynamicOptimizationEnabled,
95-
internalCallback = internalCallback,
96-
heatmapIdentifierRegistry = if (heatmapsEnabled) LazyHeatmapIdentifierRegistry(sdkCore) else null
97-
)
84+
return CapturePipelineSelector(
85+
compositionEnabled = compositionTreeRecordingEnabled,
86+
compositionFactory = compositionPipelineFactory,
87+
legacyFactory = {
88+
SessionReplayRecorder(
89+
application,
90+
resourceDataStoreManager = resourceDataStoreManager,
91+
resourcesWriter = resourceWriter,
92+
rumContextProvider = rumContextProvider,
93+
imagePrivacy = imagePrivacy,
94+
touchPrivacyManager = touchPrivacyManager,
95+
textAndInputPrivacy = textAndInputPrivacy,
96+
recordWriter = recordWriter,
97+
timeProvider = sdkCore.timeProvider,
98+
mappers = customMappers + builtInMappers(),
99+
customOptionSelectorDetectors = customOptionSelectorDetectors,
100+
customDrawableMappers = customDrawableMappers,
101+
sdkCore = sdkCore,
102+
dynamicOptimizationEnabled = dynamicOptimizationEnabled,
103+
internalCallback = internalCallback,
104+
heatmapIdentifierRegistry = if (heatmapsEnabled) {
105+
LazyHeatmapIdentifierRegistry(sdkCore)
106+
} else {
107+
null
108+
}
109+
)
110+
}
111+
).create()
98112
}
99113

100114
@Suppress("LongMethod")

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ internal class SessionReplayFeature(
7575
startRecordingImmediately: Boolean,
7676
dynamicOptimizationEnabled: Boolean,
7777
internalCallback: SessionReplayInternalCallback,
78-
heatmapsEnabled: Boolean
78+
heatmapsEnabled: Boolean,
79+
compositionTreeRecordingEnabled: Boolean
7980
) : this(
8081
sdkCore,
8182
customEndpointUrl,
@@ -95,7 +96,8 @@ internal class SessionReplayFeature(
9596
customDrawableMappers,
9697
dynamicOptimizationEnabled,
9798
internalCallback,
98-
heatmapsEnabled
99+
heatmapsEnabled,
100+
compositionTreeRecordingEnabled
99101
)
100102
)
101103

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sessionreplay.internal.composition
8+
9+
import com.datadog.android.sessionreplay.internal.recorder.Recorder
10+
11+
internal class CapturePipelineSelector(
12+
private val compositionEnabled: Boolean,
13+
private val legacyFactory: () -> Recorder,
14+
private val compositionFactory: () -> Recorder
15+
) {
16+
fun create(): Recorder = if (compositionEnabled) compositionFactory() else legacyFactory()
17+
}
18+
19+
/**
20+
* Lifecycle entry point for the composition capture pipeline. Traversal and capture are added by
21+
* later workstreams; keeping this recorder isolated prevents legacy state from crossing pipelines.
22+
*/
23+
internal class CompositionCapturePipeline : Recorder {
24+
override fun registerCallbacks() = Unit
25+
26+
override fun unregisterCallbacks() = Unit
27+
28+
override fun stopProcessingRecords() = Unit
29+
30+
override fun resumeRecorders() = Unit
31+
32+
override fun stopRecorders() = Unit
33+
}

features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ internal class SessionReplayConfigurationBuilderTest {
7676
assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty()
7777
assertThat(sessionReplayConfiguration.dynamicOptimizationEnabled).isEqualTo(true)
7878
assertThat(sessionReplayConfiguration.heatmapsEnabled).isFalse()
79+
assertThat(sessionReplayConfiguration.compositionTreeRecordingEnabled).isFalse()
7980
}
8081

8182
@Test

features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayInternalProxyTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,19 @@ internal class SessionReplayInternalProxyTest {
4848
assertThat(result).isEqualTo(testedBuilder)
4949
assertThat(sessionReplayConfiguration.internalCallback).isEqualTo(mockInternalCallback)
5050
}
51+
52+
@Test
53+
fun `M enable composition tree recording W setCompositionTreeRecordingEnabled`() {
54+
// Given
55+
testedBuilder = SessionReplayConfiguration.Builder(fakeSampleRate)
56+
testedProxy = _SessionReplayInternalProxy(testedBuilder)
57+
58+
// When
59+
val result = testedProxy.setCompositionTreeRecordingEnabled(true)
60+
val sessionReplayConfiguration = result.build()
61+
62+
// Then
63+
assertThat(result).isEqualTo(testedBuilder)
64+
assertThat(sessionReplayConfiguration.compositionTreeRecordingEnabled).isTrue()
65+
}
5166
}

0 commit comments

Comments
 (0)