Skip to content

Commit

Permalink
Merge pull request #1959 from DataDog/feature/sr-web-view-support
Browse files Browse the repository at this point in the history
Merge feature/sr-web-view-support
  • Loading branch information
xgouchet authored Apr 2, 2024
2 parents f05de15 + da8c7a4 commit a107d7c
Show file tree
Hide file tree
Showing 97 changed files with 4,165 additions and 844 deletions.
4 changes: 4 additions & 0 deletions dd-sdk-android-core/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ interface com.datadog.android.api.feature.Feature
const val TRACING_FEATURE_NAME: String
const val SESSION_REPLAY_FEATURE_NAME: String
const val NDK_CRASH_REPORTS_FEATURE_NAME: String
interface com.datadog.android.api.feature.FeatureContextUpdateReceiver
fun onContextUpdate(String, Map<String, Any?>)
interface com.datadog.android.api.feature.FeatureEventReceiver
fun onReceive(Any)
interface com.datadog.android.api.feature.FeatureScope
Expand All @@ -110,6 +112,8 @@ interface com.datadog.android.api.feature.FeatureSdkCore : com.datadog.android.a
fun updateFeatureContext(String, (MutableMap<String, Any?>) -> Unit)
fun getFeatureContext(String): Map<String, Any?>
fun setEventReceiver(String, FeatureEventReceiver)
fun setContextUpdateReceiver(String, FeatureContextUpdateReceiver)
fun removeContextUpdateReceiver(String, FeatureContextUpdateReceiver)
fun removeEventReceiver(String)
fun createSingleThreadExecutorService(): java.util.concurrent.ExecutorService
fun createScheduledExecutorService(): java.util.concurrent.ScheduledExecutorService
Expand Down
6 changes: 6 additions & 0 deletions dd-sdk-android-core/api/dd-sdk-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ public final class com/datadog/android/api/feature/Feature$Companion {
public static final field TRACING_FEATURE_NAME Ljava/lang/String;
}

public abstract interface class com/datadog/android/api/feature/FeatureContextUpdateReceiver {
public abstract fun onContextUpdate (Ljava/lang/String;Ljava/util/Map;)V
}

public abstract interface class com/datadog/android/api/feature/FeatureEventReceiver {
public abstract fun onReceive (Ljava/lang/Object;)V
}
Expand All @@ -332,7 +336,9 @@ public abstract interface class com/datadog/android/api/feature/FeatureSdkCore :
public abstract fun getFeatureContext (Ljava/lang/String;)Ljava/util/Map;
public abstract fun getInternalLogger ()Lcom/datadog/android/api/InternalLogger;
public abstract fun registerFeature (Lcom/datadog/android/api/feature/Feature;)V
public abstract fun removeContextUpdateReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureContextUpdateReceiver;)V
public abstract fun removeEventReceiver (Ljava/lang/String;)V
public abstract fun setContextUpdateReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureContextUpdateReceiver;)V
public abstract fun setEventReceiver (Ljava/lang/String;Lcom/datadog/android/api/feature/FeatureEventReceiver;)V
public abstract fun updateFeatureContext (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.api.feature

import androidx.annotation.AnyThread

/**
* Receiver for feature context updates.
*/
fun interface FeatureContextUpdateReceiver {

/**
* Called when the context for a feature is updated.
* @param featureName the name of the feature
* @param event the updated context
*/
@AnyThread
fun onContextUpdate(featureName: String, event: Map<String, Any?>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ interface FeatureSdkCore : SdkCore {
*/
fun setEventReceiver(featureName: String, receiver: FeatureEventReceiver)

/**
* Sets context update receiver for the given feature.
*
* @param featureName Feature name.
* @param listener Listener to remove.
*/
fun setContextUpdateReceiver(featureName: String, listener: FeatureContextUpdateReceiver)

/**
* Removes context update listener for the given feature.
*
* @param featureName Feature name.
* @param listener Listener to remove.
*/
fun removeContextUpdateReceiver(featureName: String, listener: FeatureContextUpdateReceiver)

/**
* Removes events receive for the given feature.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.TimeInfo
import com.datadog.android.api.context.UserInfo
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
import com.datadog.android.api.feature.FeatureEventReceiver
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.FeatureSdkCore
Expand Down Expand Up @@ -199,6 +200,11 @@ internal class DatadogCore(
val mutableContext = featureContext.toMutableMap()
updateCallback(mutableContext)
it.setFeatureContext(featureName, mutableContext)
// notify all the other features
features.filter { it.key != featureName }
.forEach { (_, feature) ->
feature.notifyContextUpdated(featureName, mutableContext.toMap())
}
}
}
}
Expand Down Expand Up @@ -229,6 +235,23 @@ internal class DatadogCore(
}
}

override fun setContextUpdateReceiver(featureName: String, listener: FeatureContextUpdateReceiver) {
val feature = features[featureName]
if (feature == null) {
internalLogger.log(
InternalLogger.Level.WARN,
InternalLogger.Target.USER,
{ MISSING_FEATURE_FOR_CONTEXT_UPDATE_LISTENER.format(Locale.US, featureName) }
)
} else {
feature.setContextUpdateListener(listener)
}
}

override fun removeContextUpdateReceiver(featureName: String, listener: FeatureContextUpdateReceiver) {
features[featureName]?.removeContextUpdateListener(listener)
}

/** @inheritDoc */
override fun removeEventReceiver(featureName: String) {
features[featureName]?.eventReceiver?.set(null)
Expand Down Expand Up @@ -542,6 +565,8 @@ internal class DatadogCore(

internal const val MISSING_FEATURE_FOR_EVENT_RECEIVER =
"Cannot add event receiver for feature \"%s\", it is not registered."
internal const val MISSING_FEATURE_FOR_CONTEXT_UPDATE_LISTENER =
"Cannot add event listener for feature \"%s\", it is not registered."
internal const val EVENT_RECEIVER_ALREADY_EXISTS =
"Feature \"%s\" already has event receiver registered, overwriting it."

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.TimeInfo
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
import com.datadog.android.api.feature.FeatureEventReceiver
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.core.internal.net.DefaultFirstPartyHostHeaderTypeResolver
Expand Down Expand Up @@ -103,6 +104,16 @@ internal object NoOpInternalSdkCore : InternalSdkCore {

override fun removeEventReceiver(featureName: String) = Unit

override fun setContextUpdateReceiver(
featureName: String,
listener: FeatureContextUpdateReceiver
) = Unit

override fun removeContextUpdateReceiver(
featureName: String,
listener: FeatureContextUpdateReceiver
) = Unit

override fun createSingleThreadExecutorService(): ExecutorService {
return NoOpExecutorService()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.Context
import com.datadog.android.api.InternalLogger
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
import com.datadog.android.api.feature.FeatureEventReceiver
import com.datadog.android.api.feature.FeatureScope
import com.datadog.android.api.feature.StorageBackedFeature
Expand Down Expand Up @@ -42,7 +43,9 @@ import com.datadog.android.core.internal.persistence.file.advanced.FeatureFileOr
import com.datadog.android.core.internal.persistence.file.batch.BatchFileReaderWriter
import com.datadog.android.core.persistence.PersistenceStrategy
import com.datadog.android.privacy.TrackingConsentProviderCallback
import java.util.Collections
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference

Expand All @@ -54,6 +57,10 @@ internal class SdkFeature(
) : FeatureScope {

internal val initialized = AtomicBoolean(false)

@Suppress("UnsafeThirdPartyFunctionCall") // the argument is always empty
internal val contextUpdateListeners =
Collections.newSetFromMap(ConcurrentHashMap<FeatureContextUpdateReceiver, Boolean>())
internal val eventReceiver = AtomicReference<FeatureEventReceiver>(null)
internal var storage: Storage = NoOpStorage()
internal var uploader: DataUploader = NoOpDataUploader()
Expand Down Expand Up @@ -166,6 +173,39 @@ internal class SdkFeature(

// endregion

// region Context Update Listener

internal fun notifyContextUpdated(featureName: String, context: Map<String, Any?>) {
contextUpdateListeners.forEach {
it.onContextUpdate(featureName, context)
}
}

internal fun setContextUpdateListener(listener: FeatureContextUpdateReceiver) {
synchronized(contextUpdateListeners) {
// the argument is always non - null, so we can suppress the warning
@Suppress("UnsafeThirdPartyFunctionCall")
if (contextUpdateListeners.contains(listener)) {
internalLogger.log(
InternalLogger.Level.WARN,
InternalLogger.Target.USER,
{ CONTEXT_UPDATE_LISTENER_ALREADY_EXISTS.format(Locale.US, wrappedFeature.name) }
)
}
contextUpdateListeners.add(listener)
}
}

internal fun removeContextUpdateListener(listener: FeatureContextUpdateReceiver) {
synchronized(contextUpdateListeners) {
@Suppress("UnsafeThirdPartyFunctionCall")
// the argument is always non - null, so we can suppress the warning
contextUpdateListeners.remove(listener)
}
}

// endregion

// region Internal

private fun setupMetricsDispatcher(
Expand Down Expand Up @@ -318,6 +358,8 @@ internal class SdkFeature(
// endregion

companion object {
internal const val CONTEXT_UPDATE_LISTENER_ALREADY_EXISTS =
"Feature \"%s\" already has this listener registered."
const val NO_EVENT_RECEIVER =
"Feature \"%s\" has no event receiver registered, ignoring event."
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.TimeInfo
import com.datadog.android.api.context.UserInfo
import com.datadog.android.api.feature.Feature
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
import com.datadog.android.api.feature.FeatureEventReceiver
import com.datadog.android.core.configuration.Configuration
import com.datadog.android.core.internal.ContextProvider
Expand Down Expand Up @@ -62,10 +63,12 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
import java.util.Locale
Expand Down Expand Up @@ -268,11 +271,17 @@ internal class DatadogCoreTest {
@MapForgery(
key = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]),
value = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)])
) context: Map<String, String>
) context: Map<String, String>,
forge: Forge
) {
// Given
val mockContextProvider = mock<ContextProvider>()
testedCore.features[feature] = mock()
val mockFeature = mock<SdkFeature>()
val otherFeatures = mapOf(
forge.anAlphaNumericalString() to mock<SdkFeature>()
)
testedCore.features[feature] = mockFeature
testedCore.features.putAll(otherFeatures)
testedCore.coreFeature.contextProvider = mockContextProvider

// When
Expand All @@ -282,6 +291,11 @@ internal class DatadogCoreTest {

// Then
verify(mockContextProvider).setFeatureContext(feature, context)
otherFeatures.forEach { (_, otherFeature) ->
verify(otherFeature).notifyContextUpdated(feature, context)
verifyNoMoreInteractions(otherFeature)
}
verify(mockFeature, never()).notifyContextUpdated(feature, context)
}

@Test
Expand Down Expand Up @@ -385,6 +399,56 @@ internal class DatadogCoreTest {
verify(mockEventReceiverRef).set(null)
}

@Test
fun `𝕄 set context update listener 𝕎 setContextUpdateListener()`(
@StringForgery feature: String
) {
// Given
val mockFeature = mock<SdkFeature>()
val mockContextUpdateListener: FeatureContextUpdateReceiver = mock()
testedCore.features[feature] = mockFeature

// When
testedCore.setContextUpdateReceiver(feature, mockContextUpdateListener)

// Then
verify(mockFeature).setContextUpdateListener(mockContextUpdateListener)
}

@Test
fun `𝕄 notify no feature registered 𝕎 setContextUpdateListener() { feature is not registered }`(
@StringForgery feature: String
) {
// Given
val mockContextUpdateListener: FeatureContextUpdateReceiver = mock()

// When
testedCore.setContextUpdateReceiver(feature, mockContextUpdateListener)

// Then
mockInternalLogger.verifyLog(
InternalLogger.Level.WARN,
InternalLogger.Target.USER,
DatadogCore.MISSING_FEATURE_FOR_CONTEXT_UPDATE_LISTENER.format(Locale.US, feature)
)
}

@Test
fun `𝕄 remove context update listener 𝕎 removeContextUpdateListener()`(
@StringForgery feature: String
) {
// Given
val mockFeature = mock<SdkFeature>()
val mockContextUpdateListener: FeatureContextUpdateReceiver = mock()
testedCore.features[feature] = mockFeature

// When
testedCore.removeContextUpdateReceiver(feature, mockContextUpdateListener)

// Then
verify(mockFeature).removeContextUpdateListener(mockContextUpdateListener)
}

@Test
fun `𝕄 provide name 𝕎 name(){}`() {
// When+Then
Expand Down
Loading

0 comments on commit a107d7c

Please sign in to comment.