Skip to content

Commit

Permalink
Merge branch 'marcosaia/RUM-7018/support-image-recording' into marcos…
Browse files Browse the repository at this point in the history
…aia/release/v2.5.0-rc0
  • Loading branch information
marco-saia-datadog committed Dec 18, 2024
2 parents a8a041a + 948d5a7 commit 9ee9b76
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 32 deletions.
8 changes: 4 additions & 4 deletions packages/core/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compileOnly "com.squareup.okhttp3:okhttp:3.12.13"

implementation "com.datadoghq:dd-sdk-android-rum:2.14.0"
implementation "com.datadoghq:dd-sdk-android-logs:2.14.0"
implementation "com.datadoghq:dd-sdk-android-trace:2.14.0"
implementation "com.datadoghq:dd-sdk-android-webview:2.14.0"
implementation "com.datadoghq:dd-sdk-android-rum:2.16.1"
implementation "com.datadoghq:dd-sdk-android-logs:2.16.1"
implementation "com.datadoghq:dd-sdk-android-trace:2.16.1"
implementation "com.datadoghq:dd-sdk-android-webview:2.16.1"
implementation "com.google.code.gson:gson:2.10.0"
testImplementation "org.junit.platform:junit-platform-launcher:1.6.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2"
Expand Down
3 changes: 2 additions & 1 deletion packages/react-native-session-replay/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ dependencies {
api "com.facebook.react:react-android:$reactNativeVersion"
}
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.datadoghq:dd-sdk-android-session-replay:2.14.0"
implementation "com.datadoghq:dd-sdk-android-session-replay:2.16.1"
implementation "com.datadoghq:dd-sdk-android-internal:2.16.1"
implementation project(path: ':datadog_mobile-react-native')

testImplementation "org.junit.platform:junit-platform-launcher:1.6.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.ExtensionSupport
import com.datadog.android.sessionreplay.MapperTypeWrapper
import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
import com.datadog.reactnative.sessionreplay.mappers.ReactEditTextMapper
import com.datadog.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper
import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.views.image.ReactImageView
import com.facebook.react.views.text.ReactTextView
import com.facebook.react.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
Expand All @@ -24,21 +27,21 @@ internal class ReactNativeSessionReplayExtensionSupport(
private val reactContext: ReactContext,
private val logger: InternalLogger
) : ExtensionSupport {
override fun name(): String {
return ReactNativeSessionReplayExtensionSupport::class.java.simpleName
}

override fun getCustomViewMappers(): List<MapperTypeWrapper<*>> {
val uiManagerModule = getUiManagerModule()

return listOf(
MapperTypeWrapper(ReactImageView::class.java, ReactNativeImageViewMapper()),
MapperTypeWrapper(ReactViewGroup::class.java, ReactViewGroupMapper()),
MapperTypeWrapper(ReactTextView::class.java, ReactTextMapper(reactContext, uiManagerModule)),
MapperTypeWrapper(ReactEditText::class.java, ReactEditTextMapper(reactContext, uiManagerModule)),
)
}

override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
return listOf()
}

@VisibleForTesting
internal fun getUiManagerModule(): UIManagerModule? {
return try {
Expand All @@ -54,6 +57,14 @@ internal class ReactNativeSessionReplayExtensionSupport(
}
}

override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
return listOf()
}

override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
return emptyList()
}

internal companion object {
internal const val RESOLVE_UIMANAGERMODULE_ERROR = "Unable to resolve UIManagerModule"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import ReactViewBackgroundDrawableUtils
import android.view.Gravity
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import com.datadog.android.internal.utils.densityNormalized
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
Expand Down Expand Up @@ -134,7 +134,7 @@ internal class ReactTextPropertiesResolver(
val fontFamily = getFontFamily(shadowNodeWrapper)
?: textWireframe.textStyle.family
val fontSize = getFontSize(shadowNodeWrapper)
?.convertToDensityNormalized(pixelsDensity)
?.densityNormalized(pixelsDensity)
?: textWireframe.textStyle.size
val fontColor = getTextColor(shadowNodeWrapper)
?: textWireframe.textStyle.color
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* 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.reactnative.sessionreplay.extensions

import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Bitmap.Config
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.VectorDrawable
import android.widget.ImageView
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import com.facebook.drawee.drawable.ArrayDrawable
import com.facebook.drawee.drawable.ForwardingDrawable
import com.facebook.drawee.drawable.RoundedBitmapDrawable
import com.facebook.drawee.drawable.ScaleTypeDrawable
import com.facebook.drawee.drawable.ScalingUtils

internal fun ScaleTypeDrawable.imageViewScaleType(): ImageView.ScaleType? {
return when (this.scaleType) {
ScalingUtils.ScaleType.CENTER -> ImageView.ScaleType.CENTER
ScalingUtils.ScaleType.CENTER_CROP -> ImageView.ScaleType.CENTER_CROP
ScalingUtils.ScaleType.CENTER_INSIDE -> ImageView.ScaleType.CENTER_INSIDE
ScalingUtils.ScaleType.FIT_CENTER -> ImageView.ScaleType.FIT_CENTER
ScalingUtils.ScaleType.FIT_START -> ImageView.ScaleType.FIT_START
ScalingUtils.ScaleType.FIT_END -> ImageView.ScaleType.FIT_END
ScalingUtils.ScaleType.FIT_XY -> ImageView.ScaleType.FIT_XY
else -> null
}
}

internal fun ArrayDrawable.getScaleTypeDrawable(): ScaleTypeDrawable? {
for (i in 0 until this.numberOfLayers) {
try {
(this.getDrawable(i) as? ScaleTypeDrawable)?.let {
return it
}
} catch(_: IllegalArgumentException) { }
}

return null
}

internal fun ArrayDrawable.getDrawableOrNull(index: Int): Drawable? {
return try {
this.getDrawable(index)
} catch (_: IllegalArgumentException) {
null
}
}

internal fun ForwardingDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
val forwardedDrawable = this.drawable
return if (forwardedDrawable != null) {
forwardedDrawable.tryToExtractBitmap(resources)
} else {
this.toBitmapOrNull(
this.intrinsicWidth,
this.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
}
}

internal fun RoundedBitmapDrawable.tryToExtractBitmap(): Bitmap? {
val privateBitmap = try {
val field = RoundedBitmapDrawable::class.java.getDeclaredField("mBitmap")
field.isAccessible = true
field.get(this) as? Bitmap
} catch (_: NoSuchFieldException) {
null
} catch (_: IllegalAccessException) {
null
} catch (_: Exception) {
null
}

return privateBitmap ?: this.toBitmapOrNull(
this.intrinsicWidth,
this.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
}

internal fun BitmapDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
if (this.bitmap != null) {
return this.bitmap
}

if (this.constantState != null) {
val copy = this.constantState?.newDrawable(resources)
return (copy as? BitmapDrawable)?.bitmap ?: copy?.toBitmapOrNull(
this.intrinsicWidth,
this.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
}

return null
}

internal fun ArrayDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
var width = 0
var height = 0
for (index in 0 until this.numberOfLayers) {
val drawable = this.getDrawableOrNull(index) ?: continue

if (drawable is ScaleTypeDrawable) {
return drawable.tryToExtractBitmap(resources)
}

if (drawable.intrinsicWidth * drawable.intrinsicHeight > width * height) {
width = drawable.intrinsicWidth
height = drawable.intrinsicHeight
}
}

return if (width > 0 && height > 0)
this.toBitmapOrNull(width, height, Bitmap.Config.ARGB_8888)
else
null
}

internal fun Drawable.tryToExtractBitmap(
resources: Resources
): Bitmap? {
when (this) {
is ArrayDrawable -> {
return this.tryToExtractBitmap(resources)
}
is ForwardingDrawable -> {
return this.tryToExtractBitmap(resources)
}
is RoundedBitmapDrawable -> {
return this.tryToExtractBitmap()
}
is BitmapDrawable -> {
return this.tryToExtractBitmap(resources)
}
is VectorDrawable, is ShapeDrawable, is DrawerArrowDrawable -> {
return this.toBitmapOrNull(
this.intrinsicWidth,
this.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
}
else -> return null
}
}

internal fun Drawable.toBitmapOrNull(
width: Int = intrinsicWidth,
height: Int = intrinsicHeight,
config: Config? = null
): Bitmap? {
if (this is BitmapDrawable && bitmap == null) {
return null
}
return toBitmap(width, height, config)
}

internal fun Drawable.toBitmap(
width: Int = intrinsicWidth,
height: Int = intrinsicHeight,
config: Config? = null
): Bitmap {
if (this is BitmapDrawable) {
if (bitmap == null) {
// This is slightly better than returning an empty, zero-size bitmap.
throw IllegalArgumentException("bitmap is null")
}
if (config == null || bitmap.config == config) {
// Fast-path to return original. Bitmap.createScaledBitmap will do this check, but it
// involves allocation and two jumps into native code so we perform the check ourselves.
if (width == bitmap.width && height == bitmap.height) {
return bitmap
}
return Bitmap.createScaledBitmap(bitmap, width, height, true)
}
}

val bitmap = Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
setBounds(0, 0, width, height)
draw(Canvas(bitmap))

setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom)
return bitmap
}
Loading

0 comments on commit 9ee9b76

Please sign in to comment.