diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt
index 5d9429eede..1855de50bb 100644
--- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt
+++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt
@@ -15,6 +15,7 @@ import android.widget.CheckedTextView
import android.widget.EditText
import android.widget.ImageView
import android.widget.NumberPicker
+import android.widget.ProgressBar
import android.widget.RadioButton
import android.widget.SeekBar
import android.widget.TextView
@@ -31,6 +32,7 @@ import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckBoxMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckedTextViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.ImageViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.NumberPickerMapper
+import com.datadog.android.sessionreplay.internal.recorder.mapper.ProgressBarWireframeMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.RadioButtonMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWireframeMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.SwitchCompatMapper
@@ -184,6 +186,16 @@ internal class DefaultRecorderProvider(
viewBoundsResolver,
drawableToColorMapper
)
+ ),
+ MapperTypeWrapper(
+ ProgressBar::class.java,
+ ProgressBarWireframeMapper(
+ viewIdentifierResolver,
+ colorStringFormatter,
+ viewBoundsResolver,
+ drawableToColorMapper,
+ true
+ )
)
)
diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt
new file mode 100644
index 0000000000..8698aa168c
--- /dev/null
+++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.sessionreplay.internal.recorder.mapper
+
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.os.Build
+import android.os.Build.VERSION
+import android.widget.ProgressBar
+import androidx.annotation.RequiresApi
+import androidx.annotation.UiThread
+import com.datadog.android.api.InternalLogger
+import com.datadog.android.sessionreplay.SessionReplayPrivacy
+import com.datadog.android.sessionreplay.internal.recorder.densityNormalized
+import com.datadog.android.sessionreplay.model.MobileSegment
+import com.datadog.android.sessionreplay.recorder.MappingContext
+import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
+import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
+import com.datadog.android.sessionreplay.utils.ColorStringFormatter
+import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
+import com.datadog.android.sessionreplay.utils.GlobalBounds
+import com.datadog.android.sessionreplay.utils.OPAQUE_ALPHA_VALUE
+import com.datadog.android.sessionreplay.utils.PARTIALLY_OPAQUE_ALPHA_VALUE
+import com.datadog.android.sessionreplay.utils.ViewBoundsResolver
+import com.datadog.android.sessionreplay.utils.ViewIdentifierResolver
+
+internal open class ProgressBarWireframeMapper
(
+ viewIdentifierResolver: ViewIdentifierResolver,
+ colorStringFormatter: ColorStringFormatter,
+ viewBoundsResolver: ViewBoundsResolver,
+ drawableToColorMapper: DrawableToColorMapper,
+ val showProgressWhenMaskUserInput: Boolean
+) : BaseAsyncBackgroundWireframeMapper
(
+ viewIdentifierResolver,
+ colorStringFormatter,
+ viewBoundsResolver,
+ drawableToColorMapper
+) {
+
+ @UiThread
+ override fun map(
+ view: P,
+ mappingContext: MappingContext,
+ asyncJobStatusCallback: AsyncJobStatusCallback,
+ internalLogger: InternalLogger
+ ): List {
+ val wireframes = mutableListOf()
+
+ // add background if needed
+ wireframes.addAll(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger))
+
+ val screenDensity = mappingContext.systemInformation.screenDensity
+ val viewPaddedBounds = viewBoundsResolver.resolveViewPaddedBounds(view, screenDensity)
+ val trackHeight = TRACK_HEIGHT_IN_PX.densityNormalized(screenDensity)
+ val trackBounds = GlobalBounds(
+ x = viewPaddedBounds.x,
+ y = viewPaddedBounds.y + (viewPaddedBounds.height - trackHeight) / 2,
+ width = viewPaddedBounds.width,
+ height = trackHeight
+ )
+
+ val defaultColor = getDefaultColor(view)
+ val trackColor = getColor(view.progressTintList, view.drawableState) ?: defaultColor
+
+ buildNonActiveTrackWireframe(view, trackBounds, trackColor)?.let(wireframes::add)
+
+ val hasProgress = !view.isIndeterminate
+ val showProgress = (mappingContext.privacy == SessionReplayPrivacy.ALLOW) ||
+ (mappingContext.privacy == SessionReplayPrivacy.MASK_USER_INPUT && showProgressWhenMaskUserInput)
+
+ if (hasProgress && showProgress) {
+ val normalizedProgress = normalizedProgress(view)
+ mapDeterminate(
+ wireframes = wireframes,
+ view = view,
+ mappingContext = mappingContext,
+ asyncJobStatusCallback = asyncJobStatusCallback,
+ internalLogger = internalLogger,
+ trackBounds = trackBounds,
+ trackColor = trackColor,
+ normalizedProgress = normalizedProgress
+ )
+ }
+
+ return wireframes
+ }
+
+ protected open fun mapDeterminate(
+ wireframes: MutableList,
+ view: P,
+ mappingContext: MappingContext,
+ asyncJobStatusCallback: AsyncJobStatusCallback,
+ internalLogger: InternalLogger,
+ trackBounds: GlobalBounds,
+ trackColor: Int,
+ normalizedProgress: Float
+ ) {
+ buildActiveTrackWireframe(view, trackBounds, normalizedProgress, trackColor)?.let(wireframes::add)
+ }
+
+ private fun buildNonActiveTrackWireframe(
+ view: P,
+ trackBounds: GlobalBounds,
+ trackColor: Int
+ ): MobileSegment.Wireframe? {
+ val nonActiveTrackId = viewIdentifierResolver.resolveChildUniqueIdentifier(view, NON_ACTIVE_TRACK_KEY_NAME)
+ ?: return null
+ val backgroundColor = colorStringFormatter.formatColorAndAlphaAsHexString(
+ trackColor,
+ PARTIALLY_OPAQUE_ALPHA_VALUE
+ )
+ return MobileSegment.Wireframe.ShapeWireframe(
+ id = nonActiveTrackId,
+ x = trackBounds.x,
+ y = trackBounds.y,
+ width = trackBounds.width,
+ height = trackBounds.height,
+ shapeStyle = MobileSegment.ShapeStyle(
+ backgroundColor = backgroundColor,
+ opacity = view.alpha
+ )
+ )
+ }
+
+ private fun buildActiveTrackWireframe(
+ view: P,
+ trackBounds: GlobalBounds,
+ normalizedProgress: Float,
+ trackColor: Int
+ ): MobileSegment.Wireframe? {
+ val activeTrackId = viewIdentifierResolver.resolveChildUniqueIdentifier(view, ACTIVE_TRACK_KEY_NAME)
+ ?: return null
+ val backgroundColor = colorStringFormatter.formatColorAndAlphaAsHexString(
+ trackColor,
+ OPAQUE_ALPHA_VALUE
+ )
+ return MobileSegment.Wireframe.ShapeWireframe(
+ id = activeTrackId,
+ x = trackBounds.x,
+ y = trackBounds.y,
+ width = (trackBounds.width * normalizedProgress).toLong(),
+ height = trackBounds.height,
+ shapeStyle = MobileSegment.ShapeStyle(
+ backgroundColor = backgroundColor,
+ opacity = view.alpha
+ )
+ )
+ }
+
+ private fun normalizedProgress(view: P): Float {
+ return if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ normalizedProgressAndroidO(view)
+ } else {
+ normalizedProgressLegacy(view)
+ }
+ }
+
+ private fun normalizedProgressLegacy(view: P): Float {
+ val range = view.max.toFloat()
+ return if (view.max == 0) {
+ 0f
+ } else {
+ view.progress.toFloat() / range
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ private fun normalizedProgressAndroidO(view: P): Float {
+ val range = view.max.toFloat() - view.min.toFloat()
+ return if (range == 0f) {
+ 0f
+ } else {
+ (view.progress - view.min) / range
+ }
+ }
+
+ protected fun getColor(colorStateList: ColorStateList?, state: IntArray): Int? {
+ return colorStateList?.getColorForState(state, colorStateList.defaultColor)
+ }
+
+ protected fun getDefaultColor(view: P): Int {
+ val uiModeFlags = view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return if (uiModeFlags == Configuration.UI_MODE_NIGHT_YES) {
+ NIGHT_MODE_COLOR
+ } else {
+ DAY_MODE_COLOR
+ }
+ }
+
+ companion object {
+ internal const val NIGHT_MODE_COLOR = 0xffffff // White
+ internal const val DAY_MODE_COLOR = 0 // Black
+ internal const val ACTIVE_TRACK_KEY_NAME = "seekbar_active_track"
+ internal const val NON_ACTIVE_TRACK_KEY_NAME = "seekbar_non_active_track"
+ internal const val THUMB_KEY_NAME = "seekbar_thumb"
+
+ internal const val THUMB_SHAPE_CORNER_RADIUS = 10
+ internal const val TRACK_HEIGHT_IN_PX = 8L
+ }
+}
diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt
index 43d7314c99..fd4d6c21c8 100644
--- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt
+++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt
@@ -6,25 +6,17 @@
package com.datadog.android.sessionreplay.internal.recorder.mapper
-import android.content.res.ColorStateList
-import android.content.res.Configuration
-import android.os.Build
-import android.os.Build.VERSION
import android.widget.SeekBar
-import androidx.annotation.RequiresApi
-import androidx.annotation.UiThread
import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.SessionReplayPrivacy
import com.datadog.android.sessionreplay.internal.recorder.densityNormalized
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.android.sessionreplay.recorder.MappingContext
-import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapper
import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback
import com.datadog.android.sessionreplay.utils.ColorStringFormatter
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
import com.datadog.android.sessionreplay.utils.GlobalBounds
import com.datadog.android.sessionreplay.utils.OPAQUE_ALPHA_VALUE
-import com.datadog.android.sessionreplay.utils.PARTIALLY_OPAQUE_ALPHA_VALUE
import com.datadog.android.sessionreplay.utils.ViewBoundsResolver
import com.datadog.android.sessionreplay.utils.ViewIdentifierResolver
import kotlin.math.max
@@ -34,48 +26,40 @@ internal open class SeekBarWireframeMapper(
colorStringFormatter: ColorStringFormatter,
viewBoundsResolver: ViewBoundsResolver,
drawableToColorMapper: DrawableToColorMapper
-) : BaseAsyncBackgroundWireframeMapper(
- viewIdentifierResolver,
- colorStringFormatter,
- viewBoundsResolver,
- drawableToColorMapper
+) : ProgressBarWireframeMapper(
+ viewIdentifierResolver = viewIdentifierResolver,
+ colorStringFormatter = colorStringFormatter,
+ viewBoundsResolver = viewBoundsResolver,
+ drawableToColorMapper = drawableToColorMapper,
+ showProgressWhenMaskUserInput = false
) {
- @UiThread
- override fun map(
+
+ override fun mapDeterminate(
+ wireframes: MutableList,
view: SeekBar,
mappingContext: MappingContext,
asyncJobStatusCallback: AsyncJobStatusCallback,
- internalLogger: InternalLogger
- ): List {
- val wireframes = mutableListOf()
-
- // add background if needed
- wireframes.addAll(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger))
-
- val screenDensity = mappingContext.systemInformation.screenDensity
- val viewPaddedBounds = viewBoundsResolver.resolveViewPaddedBounds(view, screenDensity)
-
- val trackHeight = TRACK_HEIGHT_IN_PX.densityNormalized(screenDensity)
- val trackBounds = GlobalBounds(
- x = viewPaddedBounds.x,
- y = viewPaddedBounds.y + (viewPaddedBounds.height - trackHeight) / 2,
- width = viewPaddedBounds.width,
- height = trackHeight
+ internalLogger: InternalLogger,
+ trackBounds: GlobalBounds,
+ trackColor: Int,
+ normalizedProgress: Float
+ ) {
+ super.mapDeterminate(
+ wireframes,
+ view,
+ mappingContext,
+ asyncJobStatusCallback,
+ internalLogger,
+ trackBounds,
+ trackColor,
+ normalizedProgress
)
- val defaultColor = getDefaultColor(view)
- val trackColor = getColor(view.progressTintList, view.drawableState) ?: defaultColor
- val thumbColor = getColor(view.thumbTintList, view.drawableState) ?: defaultColor
-
- buildNonActiveTrackWireframe(view, trackBounds, trackColor)?.let(wireframes::add)
-
if (mappingContext.privacy == SessionReplayPrivacy.ALLOW) {
- val normalizedProgress = if (VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- normalizedProgressAndroidO(view)
- } else {
- normalizedProgressLegacy(view)
- }
- buildActiveTrackWireframe(view, trackBounds, normalizedProgress, trackColor)?.let(wireframes::add)
+ val screenDensity = mappingContext.systemInformation.screenDensity
+ val trackHeight = ProgressBarWireframeMapper.TRACK_HEIGHT_IN_PX.densityNormalized(screenDensity)
+ val thumbColor = getColor(view.thumbTintList, view.drawableState) ?: getDefaultColor(view)
+
buildThumbWireframe(
view = view,
trackBounds = trackBounds,
@@ -85,57 +69,6 @@ internal open class SeekBarWireframeMapper(
thumbColor = thumbColor
)?.let(wireframes::add)
}
-
- return wireframes
- }
-
- private fun buildNonActiveTrackWireframe(
- view: SeekBar,
- trackBounds: GlobalBounds,
- trackColor: Int
- ): MobileSegment.Wireframe? {
- val nonActiveTrackId = viewIdentifierResolver.resolveChildUniqueIdentifier(view, NON_ACTIVE_TRACK_KEY_NAME)
- ?: return null
- val backgroundColor = colorStringFormatter.formatColorAndAlphaAsHexString(
- trackColor,
- PARTIALLY_OPAQUE_ALPHA_VALUE
- )
- return MobileSegment.Wireframe.ShapeWireframe(
- id = nonActiveTrackId,
- x = trackBounds.x,
- y = trackBounds.y,
- width = trackBounds.width,
- height = trackBounds.height,
- shapeStyle = MobileSegment.ShapeStyle(
- backgroundColor = backgroundColor,
- opacity = view.alpha
- )
- )
- }
-
- private fun buildActiveTrackWireframe(
- view: SeekBar,
- trackBounds: GlobalBounds,
- normalizedProgress: Float,
- trackColor: Int
- ): MobileSegment.Wireframe? {
- val activeTrackId = viewIdentifierResolver.resolveChildUniqueIdentifier(view, ACTIVE_TRACK_KEY_NAME)
- ?: return null
- val backgroundColor = colorStringFormatter.formatColorAndAlphaAsHexString(
- trackColor,
- OPAQUE_ALPHA_VALUE
- )
- return MobileSegment.Wireframe.ShapeWireframe(
- id = activeTrackId,
- x = trackBounds.x,
- y = trackBounds.y,
- width = (trackBounds.width * normalizedProgress).toLong(),
- height = trackBounds.height,
- shapeStyle = MobileSegment.ShapeStyle(
- backgroundColor = backgroundColor,
- opacity = view.alpha
- )
- )
}
private fun buildThumbWireframe(
@@ -166,38 +99,6 @@ internal open class SeekBarWireframeMapper(
)
}
- private fun normalizedProgressLegacy(view: SeekBar): Float {
- val range = view.max.toFloat()
- return if (view.max == 0) {
- 0f
- } else {
- view.progress.toFloat() / range
- }
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- private fun normalizedProgressAndroidO(view: SeekBar): Float {
- val range = view.max.toFloat() - view.min.toFloat()
- return if (range == 0f) {
- 0f
- } else {
- (view.progress - view.min) / range
- }
- }
-
- private fun getColor(colorStateList: ColorStateList?, state: IntArray): Int? {
- return colorStateList?.getColorForState(state, colorStateList.defaultColor)
- }
-
- private fun getDefaultColor(view: SeekBar): Int {
- val uiModeFlags = view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- return if (uiModeFlags == Configuration.UI_MODE_NIGHT_YES) {
- NIGHT_MODE_COLOR
- } else {
- DAY_MODE_COLOR
- }
- }
-
companion object {
internal const val NIGHT_MODE_COLOR = 0xffffff // White
internal const val DAY_MODE_COLOR = 0 // Black
diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt
new file mode 100644
index 0000000000..362cf543bd
--- /dev/null
+++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt
@@ -0,0 +1,388 @@
+package com.datadog.android.sessionreplay.internal.recorder.mapper
+
+import android.content.res.ColorStateList
+import android.graphics.Rect
+import android.os.Build
+import android.widget.ProgressBar
+import com.datadog.android.sessionreplay.SessionReplayPrivacy
+import com.datadog.android.sessionreplay.forge.ForgeConfigurator
+import com.datadog.android.sessionreplay.internal.recorder.densityNormalized
+import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWireframeMapper.Companion.TRACK_HEIGHT_IN_PX
+import com.datadog.android.sessionreplay.model.MobileSegment
+import com.datadog.android.sessionreplay.recorder.mapper.AbstractWireframeMapperTest
+import com.datadog.android.sessionreplay.utils.OPAQUE_ALPHA_VALUE
+import com.datadog.android.sessionreplay.utils.PARTIALLY_OPAQUE_ALPHA_VALUE
+import com.datadog.tools.unit.annotations.TestTargetApi
+import com.datadog.tools.unit.extensions.ApiLevelExtension
+import fr.xgouchet.elmyr.annotation.FloatForgery
+import fr.xgouchet.elmyr.annotation.IntForgery
+import fr.xgouchet.elmyr.annotation.LongForgery
+import fr.xgouchet.elmyr.annotation.StringForgery
+import fr.xgouchet.elmyr.junit5.ForgeConfiguration
+import fr.xgouchet.elmyr.junit5.ForgeExtension
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.extension.Extensions
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.junit.jupiter.MockitoSettings
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@Extensions(
+ ExtendWith(MockitoExtension::class),
+ ExtendWith(ForgeExtension::class),
+ ExtendWith(ApiLevelExtension::class)
+)
+@MockitoSettings(strictness = Strictness.LENIENT)
+@ForgeConfiguration(ForgeConfigurator::class)
+internal class ProgressBarWireframeMapperTest :
+ AbstractWireframeMapperTest>() {
+
+ @LongForgery
+ var fakeActiveTrackId: Long = 0L
+
+ @LongForgery
+ var fakeNonActiveTrackId: Long = 0L
+
+ @IntForgery(min = 0, max = 512)
+ var fakeMinValue: Int = 0
+
+ @IntForgery(min = 512, max = 65536)
+ var fakeMaxValue: Int = 0
+
+ @FloatForgery(min = 0f, max = 1f)
+ var fakeProgress: Float = 0f
+
+ @IntForgery
+ lateinit var fakeDrawableState: List
+
+ @IntForgery
+ var fakeTrackColor: Int = 0
+
+ @StringForgery(regex = "#[0-9A-Fa-f]{8}")
+ lateinit var fakeNonActiveTrackHtmlColor: String
+
+ @StringForgery(regex = "#[0-9A-Fa-f]{8}")
+ lateinit var fakeActiveTrackHtmlColor: String
+
+ @Mock
+ lateinit var mockProgressTintList: ColorStateList
+
+ @Mock
+ lateinit var mockThumbBounds: Rect
+
+ lateinit var expectedActiveTrackWireframe: MobileSegment.Wireframe
+ lateinit var expectedNonActiveTrackWireframe: MobileSegment.Wireframe
+
+ @BeforeEach
+ fun `set up`() {
+ testedWireframeMapper = ProgressBarWireframeMapper(
+ mockViewIdentifierResolver,
+ mockColorStringFormatter,
+ mockViewBoundsResolver,
+ mockDrawableToColorMapper,
+ showProgressWhenMaskUserInput = true
+ )
+ }
+
+ // region Indeterminate
+
+ @Test
+ fun `M return generic wireframes W map {indeterminate}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = true)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ }
+
+ // endregion
+
+ // region Android O+, determinate
+
+ @Test
+ @TestTargetApi(Build.VERSION_CODES.O)
+ fun `M return partial wireframes W map {determinate, invalid track id, Android 0+}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.ACTIVE_TRACK_KEY_NAME, null)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ }
+
+ @Test
+ @TestTargetApi(Build.VERSION_CODES.O)
+ fun `M return partial wireframes W map {determinate, invalid non active track id, Android 0+}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.NON_ACTIVE_TRACK_KEY_NAME, null)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedActiveTrackWireframe)
+ }
+
+ @Test
+ @TestTargetApi(Build.VERSION_CODES.O)
+ fun `M return wireframes W map {determinate, privacy=ALLOW, Android 0+}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(2)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ assertThatBoundsAreCloseEnough(wireframes[1], expectedActiveTrackWireframe)
+ }
+
+ @Test
+ @TestTargetApi(Build.VERSION_CODES.O)
+ fun `M return wireframes W map {determinate, privacy=MASK, Android 0+}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.MASK)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ }
+
+ @Test
+ @TestTargetApi(Build.VERSION_CODES.O)
+ fun `M return wireframes W map {determinate, privacy=MASK_USER_INPUT, Android 0+}`() {
+ // Given
+ withPrivacy(SessionReplayPrivacy.MASK_USER_INPUT)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(2)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ assertThatBoundsAreCloseEnough(wireframes[1], expectedActiveTrackWireframe)
+ }
+
+ // endregion
+
+ // region API < Android O, determinate
+
+ @Test
+ fun `M return partial wireframes W map {determinate, invalid track id}`() {
+ // Given
+ fakeMinValue = 0
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.ACTIVE_TRACK_KEY_NAME, null)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ }
+
+ @Test
+ fun `M return partial wireframes W map {determinate, invalid non active track id}`() {
+ // Given
+ fakeMinValue = 0
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.NON_ACTIVE_TRACK_KEY_NAME, null)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedActiveTrackWireframe)
+ }
+
+ @Test
+ fun `M return wireframes W map {determinate, privacy=ALLOW}`() {
+ // Given
+ fakeMinValue = 0
+ withPrivacy(SessionReplayPrivacy.ALLOW)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(2)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ assertThatBoundsAreCloseEnough(wireframes[1], expectedActiveTrackWireframe)
+ }
+
+ @Test
+ fun `M return wireframes W map {determinate, privacy=MASK}`() {
+ // Given
+ fakeMinValue = 0
+ withPrivacy(SessionReplayPrivacy.MASK)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(1)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ }
+
+ @Test
+ fun `M return wireframes W map {determinate, privacy=MASK_USER_INPUT}`() {
+ // Given
+ fakeMinValue = 0
+ withPrivacy(SessionReplayPrivacy.MASK_USER_INPUT)
+ prepareMockProgressBar(isIndeterminate = false)
+
+ // When
+ val wireframes = testedWireframeMapper.map(
+ mockMappedView,
+ fakeMappingContext,
+ mockAsyncJobStatusCallback,
+ mockInternalLogger
+ )
+
+ // Then
+ assertThat(wireframes).hasSize(2)
+ assertThatBoundsAreCloseEnough(wireframes[0], expectedNonActiveTrackWireframe)
+ assertThatBoundsAreCloseEnough(wireframes[1], expectedActiveTrackWireframe)
+ }
+
+ // endregion
+
+ // region Internal
+
+ private fun prepareMockProgressBar(isIndeterminate: Boolean) {
+ val fakeStateIntArray = fakeDrawableState.toIntArray()
+
+ withUiMode(fakeUiTypeMode)
+
+ prepareMockView { mockView ->
+ whenever(mockView.drawableState) doReturn fakeStateIntArray
+
+ whenever(mockView.progressTintList) doReturn mockProgressTintList
+
+ whenever(mockView.min) doReturn fakeMinValue
+ whenever(mockView.max) doReturn fakeMaxValue
+ whenever(mockView.progress) doReturn ((fakeMaxValue - fakeMinValue) * fakeProgress).toInt() + fakeMinValue
+ whenever(mockView.isIndeterminate) doReturn isIndeterminate
+ }
+
+ whenever(mockProgressTintList.getColorForState(eq(fakeStateIntArray), any())) doReturn fakeTrackColor
+
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.ACTIVE_TRACK_KEY_NAME, fakeActiveTrackId)
+ mockChildUniqueIdentifier(SeekBarWireframeMapper.NON_ACTIVE_TRACK_KEY_NAME, fakeNonActiveTrackId)
+
+ mockColorAndAlphaAsHexString(fakeTrackColor, OPAQUE_ALPHA_VALUE, fakeActiveTrackHtmlColor)
+ mockColorAndAlphaAsHexString(fakeTrackColor, PARTIALLY_OPAQUE_ALPHA_VALUE, fakeNonActiveTrackHtmlColor)
+
+ val screenDensity = fakeMappingContext.systemInformation.screenDensity
+ val fakeTrackHeight = TRACK_HEIGHT_IN_PX.densityNormalized(screenDensity)
+ val fakeTrackY = fakeViewPaddedBounds.y + ((fakeViewPaddedBounds.height - fakeTrackHeight) / 2)
+
+ expectedNonActiveTrackWireframe = MobileSegment.Wireframe.ShapeWireframe(
+ id = fakeNonActiveTrackId,
+ x = fakeViewPaddedBounds.x,
+ y = fakeTrackY,
+ width = fakeViewPaddedBounds.width,
+ height = fakeTrackHeight,
+ shapeStyle = MobileSegment.ShapeStyle(
+ backgroundColor = fakeNonActiveTrackHtmlColor,
+ opacity = fakeViewAlpha
+ )
+ )
+ expectedActiveTrackWireframe = MobileSegment.Wireframe.ShapeWireframe(
+ id = fakeActiveTrackId,
+ x = fakeViewPaddedBounds.x,
+ y = fakeTrackY,
+ width = (fakeViewPaddedBounds.width * fakeProgress).toLong(),
+ height = fakeTrackHeight,
+ shapeStyle = MobileSegment.ShapeStyle(
+ backgroundColor = fakeActiveTrackHtmlColor,
+ opacity = fakeViewAlpha
+ )
+ )
+ }
+
+ // endregion
+}
diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt
index c480ac0200..4b20d8c97b 100644
--- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt
+++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt
@@ -245,7 +245,7 @@ internal class SeekBarWireframeMapperTest : AbstractWireframeMapperTest