Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -400,16 +400,18 @@ class RootSemanticsNodeMapperTest {
}

@Test
fun `M call interop callback W semantics node has interop view`(forge: Forge) {
fun `M preserve embedded wireframe W semantics node has interop view`(forge: Forge) {
// Given
val mockSemanticsNode = mockSemanticsNode(null)
val mockView = mock<View>()
val fakeInteropWireframe = MobileSegment.Wireframe.ShapeWireframe(
val fakeInteropWireframe = MobileSegment.Wireframe.EmbeddedContentWireframe(
id = forge.aLong(),
x = forge.aLong(min = 0, max = 500),
y = forge.aLong(min = 0, max = 500),
width = forge.aLong(min = 1, max = 500),
height = forge.aLong(min = 1, max = 500)
height = forge.aLong(min = 1, max = 500),
slotId = forge.anAlphabeticalString(),
isVisible = true
)
whenever(mockSemanticsUtils.getInteropView(mockSemanticsNode)) doReturn mockView
whenever(fakeMappingContext.interopViewCallback.map(mockView, fakeMappingContext))
Expand Down
4 changes: 4 additions & 0 deletions features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ enum com.datadog.android.sessionreplay.TouchPrivacy : PrivacyLevel
class com.datadog.android.sessionreplay._SessionReplayInternalProxy
constructor(SessionReplayConfiguration.Builder)
fun setInternalCallback(SessionReplayInternalCallback): SessionReplayConfiguration.Builder
companion object
fun setEmbeddedContentSlotId(android.view.View, String?)
fun addEmbeddedContentRecords(List<Map<String, Any?>>, String, String, com.datadog.android.api.SdkCore = Datadog.getInstance())
fun addEmbeddedContentResource(String, ByteArray, String, com.datadog.android.api.SdkCore = Datadog.getInstance())
class com.datadog.android.sessionreplay.internal.TouchPrivacyManager
constructor(com.datadog.android.sessionreplay.TouchPrivacy)
fun addTouchOverrideArea(android.graphics.Rect, com.datadog.android.sessionreplay.TouchPrivacy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,19 @@ public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/En
}

public final class com/datadog/android/sessionreplay/_SessionReplayInternalProxy {
public static final field Companion Lcom/datadog/android/sessionreplay/_SessionReplayInternalProxy$Companion;
public fun <init> (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;)V
public final fun setInternalCallback (Lcom/datadog/android/sessionreplay/SessionReplayInternalCallback;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
}

public final class com/datadog/android/sessionreplay/_SessionReplayInternalProxy$Companion {
public final fun addEmbeddedContentRecords (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/SdkCore;)V
public static synthetic fun addEmbeddedContentRecords$default (Lcom/datadog/android/sessionreplay/_SessionReplayInternalProxy$Companion;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V
public final fun addEmbeddedContentResource (Ljava/lang/String;[BLjava/lang/String;Lcom/datadog/android/api/SdkCore;)V
public static synthetic fun addEmbeddedContentResource$default (Lcom/datadog/android/sessionreplay/_SessionReplayInternalProxy$Companion;Ljava/lang/String;[BLjava/lang/String;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V
public final fun setEmbeddedContentSlotId (Landroid/view/View;Ljava/lang/String;)V
}

public final class com/datadog/android/sessionreplay/internal/TouchPrivacyManager {
public fun <init> (Lcom/datadog/android/sessionreplay/TouchPrivacy;)V
public final fun addTouchOverrideArea (Landroid/graphics/Rect;Lcom/datadog/android/sessionreplay/TouchPrivacy;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ object SessionReplay {
sessionReplayFeature?.manuallyStopRecording()
}

internal fun registeredFeature(): SessionReplayFeature? {
return (currentRegisteredCore?.get() as? FeatureSdkCore)
?.getFeature(Feature.SESSION_REPLAY_FEATURE_NAME)
?.unwrap<SessionReplayFeature>()
}

private fun isAlreadyRegistered() =
currentRegisteredCore?.get()?.isCoreActive() == true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@

package com.datadog.android.sessionreplay

import android.view.View
import androidx.annotation.AnyThread
import androidx.annotation.UiThread
import com.datadog.android.Datadog
import com.datadog.android.api.SdkCore
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureSdkCore
import com.datadog.android.lint.InternalApi
import com.datadog.android.sessionreplay.internal.SessionReplayFeature
import com.datadog.android.sessionreplay.internal.embedded.EmbeddedContentEvent
import com.datadog.android.sessionreplay.internal.embedded.EmbeddedContentSlotRegistration

/**
* This class exposes internal methods that are used by other Datadog modules and cross platform
Expand Down Expand Up @@ -35,4 +45,109 @@ class _SessionReplayInternalProxy(private val builder: SessionReplayConfiguratio
): SessionReplayConfiguration.Builder {
return builder.setInternalCallback(internalCallback)
}

companion object {
/**
* Identifies [view] as a host slot for embedded Session Replay content.
*
* The slot identifier is supplied by the embedding SDK and is independent from the native
* wireframe identifier. Passing `null` permanently detaches the slot and removes its
* embedded-content wireframe from the next capture. Reassigning the current slot identifier
* has no effect.
*/
@UiThread
fun setEmbeddedContentSlotId(view: View, slotId: String?) {
val previousSlotId =
view.getTag(R.id.datadog_session_replay_slot_id) as? String
if (previousSlotId == slotId) {
return
}
val previousRegistration =
view.getTag(R.id.datadog_session_replay_slot_registration)
as? EmbeddedContentSlotRegistration
val newRegistration = slotId?.let {
EmbeddedContentSlotRegistration(it)
}
previousRegistration?.deactivate()
view.setTag(R.id.datadog_session_replay_slot_id, slotId)
view.setTag(R.id.datadog_session_replay_slot_registration, newRegistration)
SessionReplay.registeredFeature()?.notifyEmbeddedContentSlotChanged(
previousRegistration = previousRegistration,
newRegistration = newRegistration
)
@Suppress("UnsafeThirdPartyFunctionCall") // Android documents no exception for scheduling invalidation.
view.postInvalidateOnAnimation()
}

/**
* Queues Session Replay records produced by an embedded renderer.
*/
@AnyThread
fun addEmbeddedContentRecords(
records: List<Map<String, Any?>>,
slotId: String,
viewId: String,
sdkCore: SdkCore = Datadog.getInstance()
) {
sendEmbeddedContentEvent(
sdkCore,
EmbeddedContentEvent.RecordBatch(snapshotRecords(records), slotId, viewId)
)
}

/**
* Queues a Session Replay resource produced by an embedded renderer.
*/
@AnyThread
fun addEmbeddedContentResource(
identifier: String,
resourceData: ByteArray,
mimeType: String,
sdkCore: SdkCore = Datadog.getInstance()
) {
sendEmbeddedContentEvent(
sdkCore,
EmbeddedContentEvent.Resource(identifier, resourceData.copyOf(), mimeType)
)
}

private fun sendEmbeddedContentEvent(sdkCore: SdkCore, event: EmbeddedContentEvent) {
(sdkCore as? FeatureSdkCore)
?.getFeature(Feature.SESSION_REPLAY_FEATURE_NAME)
?.unwrap<SessionReplayFeature>()
?.receiveEmbeddedContentEvent(event)
}

private fun snapshotRecords(records: List<Map<String, Any?>>): List<Map<String, Any?>> {
return records.map { record ->
buildMap {
record.forEach { (key, value) ->
put(key, snapshotValue(value))
}
}
}
}

@Suppress("UnsafeThirdPartyFunctionCall") // Standard collection mapping and array copies only.
private fun snapshotValue(value: Any?): Any? {
return when (value) {
is Map<*, *> -> buildMap {
value.forEach { (key, nestedValue) ->
put(key, snapshotValue(nestedValue))
}
}
is Iterable<*> -> value.map { snapshotValue(it) }
is Array<*> -> value.map { snapshotValue(it) }
is ByteArray -> value.copyOf()
is ShortArray -> value.copyOf()
is IntArray -> value.copyOf()
is LongArray -> value.copyOf()
is FloatArray -> value.copyOf()
is DoubleArray -> value.copyOf()
is BooleanArray -> value.copyOf()
is CharArray -> value.copyOf()
else -> value
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.datadog.android.sessionreplay.ImagePrivacy
import com.datadog.android.sessionreplay.MapperTypeWrapper
import com.datadog.android.sessionreplay.SessionReplayInternalCallback
import com.datadog.android.sessionreplay.TextAndInputPrivacy
import com.datadog.android.sessionreplay.internal.embedded.EmbeddedContentSlotRegistry
import com.datadog.android.sessionreplay.internal.recorder.Recorder
import com.datadog.android.sessionreplay.internal.recorder.SessionReplayRecorder
import com.datadog.android.sessionreplay.internal.recorder.mapper.ActionBarContainerMapper
Expand Down Expand Up @@ -75,7 +76,8 @@ internal class DefaultRecorderProvider(
resourceWriter: ResourcesWriter,
recordWriter: RecordWriter,
rumContextProvider: RumContextProvider,
application: Application
application: Application,
embeddedContentSlotRegistry: EmbeddedContentSlotRegistry
): Recorder {
return SessionReplayRecorder(
application,
Expand All @@ -93,6 +95,7 @@ internal class DefaultRecorderProvider(
sdkCore = sdkCore,
dynamicOptimizationEnabled = dynamicOptimizationEnabled,
internalCallback = internalCallback,
embeddedContentSlotRegistry = embeddedContentSlotRegistry,
heatmapIdentifierRegistry = if (heatmapsEnabled) LazyHeatmapIdentifierRegistry(sdkCore) else null
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.datadog.android.sessionreplay.internal

import android.app.Application
import com.datadog.android.sessionreplay.internal.embedded.EmbeddedContentSlotRegistry
import com.datadog.android.sessionreplay.internal.recorder.Recorder
import com.datadog.android.sessionreplay.internal.resources.ResourceDataStoreManager
import com.datadog.android.sessionreplay.internal.storage.RecordWriter
Expand All @@ -19,6 +20,7 @@ internal fun interface RecorderProvider {
resourceWriter: ResourcesWriter,
recordWriter: RecordWriter,
rumContextProvider: RumContextProvider,
application: Application
application: Application,
embeddedContentSlotRegistry: EmbeddedContentSlotRegistry
): Recorder
}
Loading
Loading