Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ object Rum {
rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter
)
},
insightsCollector = rumFeature.insightsCollector
insightsCollector = rumFeature.insightsCollector,
viewIdentityResolver = rumFeature.viewIdentityResolver
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,31 @@ object RumAttributes {
*/
const val INTERNAL_INSTRUMENTATION_TYPE: String = "_dd.instrumentation_type"

/**
* Stable view identity for heatmap correlation (maps to schema field permanent_id).
*/
internal const val INTERNAL_ACTION_TARGET_IDENTITY: String = "_dd.action.target.permanent_id"

/**
* Width of the action target element (in pixels).
*/
internal const val INTERNAL_ACTION_TARGET_WIDTH: String = "_dd.action.target.width"

/**
* Height of the action target element (in pixels).
*/
internal const val INTERNAL_ACTION_TARGET_HEIGHT: String = "_dd.action.target.height"

/**
* X coordinate of the touch position relative to the target element (in pixels).
*/
internal const val INTERNAL_ACTION_POSITION_X: String = "_dd.action.position.x"

/**
* Y coordinate of the touch position relative to the target element (in pixels).
*/
internal const val INTERNAL_ACTION_POSITION_Y: String = "_dd.action.position.y"

// endregion

// region Resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import com.datadog.android.event.EventMapper
import com.datadog.android.event.MapperSerializer
import com.datadog.android.event.NoOpEventMapper
import com.datadog.android.internal.flags.RumFlagEvaluationMessage
import com.datadog.android.internal.identity.NoOpViewIdentityResolver
import com.datadog.android.internal.identity.ViewIdentityResolver
import com.datadog.android.internal.identity.ViewIdentityResolverImpl
import com.datadog.android.internal.system.BuildSdkVersionProvider
import com.datadog.android.internal.telemetry.InternalTelemetryEvent
import com.datadog.android.internal.thread.isMainThread
Expand Down Expand Up @@ -178,6 +181,7 @@ internal class RumFeature(
internal var displayInfoProvider: InfoProvider<DisplayInfo> = NoOpDisplayInfoProvider()
internal val rumContextUpdateReceivers = mutableSetOf<FeatureContextUpdateReceiver>()
internal var insightsCollector: InsightsCollector = NoOpInsightsCollector()
internal var viewIdentityResolver: ViewIdentityResolver = NoOpViewIdentityResolver()

private val lateCrashEventHandler by lazy { lateCrashReporterFactory(sdkCore as InternalSdkCore) }
internal var rumAppStartupDetector: RumAppStartupDetector? = null
Expand Down Expand Up @@ -222,6 +226,12 @@ internal class RumFeature(
telemetryConfigurationSampleRate = configuration.telemetryConfigurationSampleRate
backgroundEventTracking = configuration.backgroundEventTracking
trackFrustrations = configuration.trackFrustrations
viewIdentityResolver = ViewIdentityResolverImpl(appContext.packageName)
// Store in feature context for cross-feature access (e.g., Session Replay)
sdkCore.updateFeatureContext(name) { context ->
context[ViewIdentityResolver.FEATURE_CONTEXT_KEY] = viewIdentityResolver
}

batteryInfoProvider = DefaultBatteryInfoProvider(
applicationContext = appContext,
timeProvider = sdkCore.timeProvider
Expand All @@ -238,7 +248,8 @@ internal class RumFeature(
configuration.interactionPredicate,
composeActionTrackingStrategy = configuration.composeActionTrackingStrategy,
buildSdkVersionProvider,
sdkCore.internalLogger
sdkCore.internalLogger,
viewIdentityResolver
)
} else {
NoOpUserActionTrackingStrategy()
Expand Down Expand Up @@ -354,6 +365,7 @@ internal class RumFeature(
anrDetectorRunnable?.stop()
vitalExecutorService = NoOpScheduledExecutorService()
sessionListener = NoOpRumSessionListener()
viewIdentityResolver = NoOpViewIdentityResolver()

cleanupInfoProviders()

Expand Down Expand Up @@ -863,14 +875,16 @@ internal class RumFeature(
interactionPredicate: InteractionPredicate,
composeActionTrackingStrategy: ActionTrackingStrategy,
buildSdkVersionProvider: BuildSdkVersionProvider,
internalLogger: InternalLogger
internalLogger: InternalLogger,
viewIdentityResolver: ViewIdentityResolver
): UserActionTrackingStrategy {
val gesturesTracker =
provideGestureTracker(
customProviders = touchTargetExtraAttributesProviders,
interactionPredicate = interactionPredicate,
composeActionTrackingStrategy = composeActionTrackingStrategy,
internalLogger = internalLogger
internalLogger = internalLogger,
viewIdentityResolver = viewIdentityResolver
)
return if (buildSdkVersionProvider.isAtLeastQ) {
UserActionTrackingStrategyApi29(gesturesTracker)
Expand All @@ -883,15 +897,17 @@ internal class RumFeature(
customProviders: Array<ViewAttributesProvider>,
interactionPredicate: InteractionPredicate,
composeActionTrackingStrategy: ActionTrackingStrategy,
internalLogger: InternalLogger
internalLogger: InternalLogger,
viewIdentityResolver: ViewIdentityResolver
): DatadogGesturesTracker {
val defaultProviders = arrayOf(JetpackViewAttributesProvider())
val providers = customProviders + defaultProviders
return DatadogGesturesTracker(
providers,
interactionPredicate,
composeActionsTrackingStrategy = composeActionTrackingStrategy,
internalLogger
internalLogger,
viewIdentityResolver
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,12 @@ internal class RumEventSerializer(
RumAttributes.INTERNAL_TIMESTAMP,
RumAttributes.INTERNAL_ERROR_TYPE,
RumAttributes.INTERNAL_ERROR_SOURCE_TYPE,
RumAttributes.INTERNAL_ERROR_IS_CRASH
RumAttributes.INTERNAL_ERROR_IS_CRASH,
RumAttributes.INTERNAL_ACTION_TARGET_IDENTITY,
RumAttributes.INTERNAL_ACTION_POSITION_X,
RumAttributes.INTERNAL_ACTION_POSITION_Y,
RumAttributes.INTERNAL_ACTION_TARGET_WIDTH,
RumAttributes.INTERNAL_ACTION_TARGET_HEIGHT
)

// this are attributes which may come after the calls made by cross-platform SDKs (they are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.datadog.android.api.feature.EventWriteScope
import com.datadog.android.api.storage.DataWriter
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.rum.RumActionType
import com.datadog.android.rum.RumAttributes
import com.datadog.android.rum.RumSessionType
import com.datadog.android.rum.internal.FeaturesContextResolver
import com.datadog.android.rum.internal.domain.RumContext
Expand All @@ -28,7 +29,7 @@ import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.math.max

@Suppress("LongParameterList")
@Suppress("LongParameterList", "TooManyFunctions")
internal class RumActionScope(
override val parentScope: RumScope,
private val sdkCore: InternalSdkCore,
Expand Down Expand Up @@ -71,8 +72,6 @@ internal class RumActionScope(
private var sent = false
internal var stopped = false

// endregion

@WorkerThread
override fun handleEvent(
event: RumRawEvent,
Expand Down Expand Up @@ -270,6 +269,12 @@ internal class RumActionScope(
frustrations.add(ActionEvent.Type.ERROR_TAP)
}

val viewIdentity = actionAttributes[RumAttributes.INTERNAL_ACTION_TARGET_IDENTITY] as? String
val positionX = actionAttributes[RumAttributes.INTERNAL_ACTION_POSITION_X] as? Long
val positionY = actionAttributes[RumAttributes.INTERNAL_ACTION_POSITION_Y] as? Long
val targetWidth = actionAttributes[RumAttributes.INTERNAL_ACTION_TARGET_WIDTH] as? Long
val targetHeight = actionAttributes[RumAttributes.INTERNAL_ACTION_TARGET_HEIGHT] as? Long

sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) {
val user = datadogContext.userInfo
val hasReplay = featuresContextResolver.resolveViewHasReplay(
Expand Down Expand Up @@ -354,7 +359,19 @@ internal class RumActionScope(
session = ActionEvent.DdSession(
sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition()
),
configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate)
configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate),
action = ActionEvent.DdAction(
position = positionX?.let { x ->
positionY?.let { y ->
ActionEvent.Position(x = x, y = y)
}
},
target = ActionEvent.DdActionTarget(
permanentId = viewIdentity,
width = targetWidth,
height = targetHeight
)
)
),
connectivity = networkInfo.toActionConnectivity(),
service = datadogContext.service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.datadog.android.api.storage.DataWriter
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.core.sampling.Sampler
import com.datadog.android.internal.identity.ViewIdentityResolver
import com.datadog.android.rum.DdRumContentProvider
import com.datadog.android.rum.GlobalRumMonitor
import com.datadog.android.rum.RumSessionListener
Expand Down Expand Up @@ -55,7 +56,8 @@ internal class RumApplicationScope(
private val batteryInfoProvider: InfoProvider<BatteryInfo>,
private val displayInfoProvider: InfoProvider<DisplayInfo>,
private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager,
private val insightsCollector: InsightsCollector
private val insightsCollector: InsightsCollector,
private val viewIdentityResolver: ViewIdentityResolver
) : RumScope, RumViewChangedListener {

override val parentScope: RumScope? = null
Expand Down Expand Up @@ -85,7 +87,8 @@ internal class RumApplicationScope(
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
)

Expand Down Expand Up @@ -207,7 +210,8 @@ internal class RumApplicationScope(
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
childScopes.add(newSession)
if (event !is RumRawEvent.StartView) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.datadog.android.api.storage.NoOpDataWriter
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.core.sampling.Sampler
import com.datadog.android.internal.identity.ViewIdentityResolver
import com.datadog.android.internal.profiling.ProfilerStopEvent
import com.datadog.android.rum.RumSessionListener
import com.datadog.android.rum.RumSessionType
Expand Down Expand Up @@ -60,7 +61,8 @@ internal class RumSessionScope(
private val sessionMaxDurationNanos: Long = DEFAULT_SESSION_MAX_DURATION_NS,
rumSessionTypeOverride: RumSessionType?,
private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager,
insightsCollector: InsightsCollector
insightsCollector: InsightsCollector,
viewIdentityResolver: ViewIdentityResolver
) : RumScope {

internal var sessionId = RumContext.NULL_UUID
Expand Down Expand Up @@ -97,7 +99,8 @@ internal class RumSessionScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)

internal val activeView: RumViewScope?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.datadog.android.api.storage.DataWriter
import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.core.metrics.MethodCallSamplingRate
import com.datadog.android.internal.identity.ViewIdentityResolver
import com.datadog.android.internal.telemetry.InternalTelemetryEvent
import com.datadog.android.rum.DdRumContentProvider
import com.datadog.android.rum.RumSessionType
Expand Down Expand Up @@ -60,7 +61,8 @@ internal class RumViewManagerScope(
private val accessibilitySnapshotManager: AccessibilitySnapshotManager,
private val batteryInfoProvider: InfoProvider<BatteryInfo>,
private val displayInfoProvider: InfoProvider<DisplayInfo>,
private val insightsCollector: InsightsCollector
private val insightsCollector: InsightsCollector,
private val viewIdentityResolver: ViewIdentityResolver
) : RumScope {

private val interactionToNextViewMetricResolver: InteractionToNextViewMetricResolver =
Expand Down Expand Up @@ -295,7 +297,8 @@ internal class RumViewManagerScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
applicationDisplayed = true
childrenScopes.add(viewScope)
Expand Down Expand Up @@ -378,7 +381,8 @@ internal class RumViewManagerScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
}

Expand Down Expand Up @@ -421,7 +425,8 @@ internal class RumViewManagerScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.datadog.android.core.InternalSdkCore
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
import com.datadog.android.internal.attributes.LocalAttribute
import com.datadog.android.internal.attributes.ViewScopeInstrumentationType
import com.datadog.android.internal.identity.ViewIdentityResolver
import com.datadog.android.internal.telemetry.InternalTelemetryEvent
import com.datadog.android.internal.utils.loggableStackTrace
import com.datadog.android.rum.RumActionType
Expand Down Expand Up @@ -89,7 +90,8 @@ internal open class RumViewScope(
private val accessibilitySnapshotManager: AccessibilitySnapshotManager,
private val batteryInfoProvider: InfoProvider<BatteryInfo>,
private val displayInfoProvider: InfoProvider<DisplayInfo>,
private val insightsCollector: InsightsCollector
private val insightsCollector: InsightsCollector,
private val viewIdentityResolver: ViewIdentityResolver
) : RumScope {

internal val url = key.url.replace('.', '/')
Expand Down Expand Up @@ -182,6 +184,16 @@ internal open class RumViewScope(
cpuVitalMonitor.register(cpuVitalListener)
memoryVitalMonitor.register(memoryVitalListener)
frameRateVitalMonitor.register(frameRateVitalListener)

viewIdentityResolver.setCurrentScreen(url)

val rumContext = parentScope.getRumContext()
if (rumContext.syntheticsTestId != null) {
logSynthetics("_dd.application.id", rumContext.applicationId)
logSynthetics("_dd.session.id", rumContext.sessionId)
logSynthetics("_dd.view.id", viewId)
}

networkSettledMetricResolver.viewWasCreated(eventTime.nanoTime)
interactionToNextViewMetricResolver.onViewCreated(viewId, eventTime.nanoTime)
slowFramesListener?.onViewCreated(viewId, startedNanos)
Expand Down Expand Up @@ -472,7 +484,8 @@ internal open class RumViewScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
}

Expand Down Expand Up @@ -1676,7 +1689,8 @@ internal open class RumViewScope(
accessibilitySnapshotManager: AccessibilitySnapshotManager,
batteryInfoProvider: InfoProvider<BatteryInfo>,
displayInfoProvider: InfoProvider<DisplayInfo>,
insightsCollector: InsightsCollector
insightsCollector: InsightsCollector,
viewIdentityResolver: ViewIdentityResolver
): RumViewScope {
val networkSettledMetricResolver = NetworkSettledMetricResolver(
networkSettledResourceIdentifier,
Expand Down Expand Up @@ -1713,7 +1727,8 @@ internal open class RumViewScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
insightsCollector = insightsCollector
insightsCollector = insightsCollector,
viewIdentityResolver = viewIdentityResolver
)
}

Expand Down
Loading
Loading