-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Android: added ReactImageViewMapper for SR Image Recording
- Loading branch information
1 parent
7e201e3
commit baf0cfa
Showing
12 changed files
with
557 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
...replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/IntExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
* 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 | ||
|
||
internal fun Int.densityNormalized(density: Float): Int { | ||
if (density == 0f) { | ||
return this | ||
} | ||
return (this / density).toInt() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
...oid/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
/* | ||
* 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.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 androidx.core.graphics.drawable.toBitmapOrNull | ||
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 | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
...c/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* 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.mappers | ||
|
||
import com.datadog.android.api.InternalLogger | ||
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.DefaultColorStringFormatter | ||
import com.datadog.android.sessionreplay.utils.DefaultViewBoundsResolver | ||
import com.datadog.android.sessionreplay.utils.DefaultViewIdentifierResolver | ||
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper | ||
import com.datadog.reactnative.sessionreplay.extensions.densityNormalized | ||
import com.datadog.reactnative.sessionreplay.extensions.getScaleTypeDrawable | ||
import com.datadog.reactnative.sessionreplay.extensions.imageViewScaleType | ||
import com.datadog.reactnative.sessionreplay.resources.ReactDrawableCopier | ||
import com.datadog.reactnative.sessionreplay.utils.ImageViewUtils | ||
import com.facebook.drawee.drawable.FadeDrawable | ||
import com.facebook.react.views.image.ReactImageView | ||
|
||
internal class ReactNativeImageViewMapper: BaseAsyncBackgroundWireframeMapper<ReactImageView>( | ||
viewIdentifierResolver = DefaultViewIdentifierResolver, | ||
colorStringFormatter = DefaultColorStringFormatter, | ||
viewBoundsResolver = DefaultViewBoundsResolver, | ||
drawableToColorMapper = DrawableToColorMapper.getDefault() | ||
) { | ||
private val drawableCopier = ReactDrawableCopier() | ||
|
||
override fun map( | ||
view: ReactImageView, | ||
mappingContext: MappingContext, | ||
asyncJobStatusCallback: AsyncJobStatusCallback, | ||
internalLogger: InternalLogger | ||
): List<MobileSegment.Wireframe> { | ||
val wireframes = mutableListOf<MobileSegment.Wireframe>() | ||
wireframes.addAll(super.map(view, mappingContext, asyncJobStatusCallback, internalLogger)) | ||
|
||
val drawable = view.drawable?.current ?: return wireframes | ||
|
||
val parentRect = ImageViewUtils.resolveParentRectAbsPosition(view) | ||
val scaleType = (drawable as? FadeDrawable) | ||
?.getScaleTypeDrawable() | ||
?.imageViewScaleType() ?: view.scaleType | ||
val contentRect = ImageViewUtils.resolveContentRectWithScaling(view, drawable, scaleType) | ||
|
||
val resources = view.resources | ||
val density = resources.displayMetrics.density | ||
|
||
val clipping = if (view.cropToPadding) { | ||
ImageViewUtils.calculateClipping(parentRect, contentRect, density) | ||
} else { | ||
null | ||
} | ||
|
||
val contentXPosInDp = contentRect.left.densityNormalized(density).toLong() | ||
val contentYPosInDp = contentRect.top.densityNormalized(density).toLong() | ||
val contentWidthPx = contentRect.width() | ||
val contentHeightPx = contentRect.height() | ||
|
||
// resolve foreground | ||
mappingContext.imageWireframeHelper.createImageWireframeByDrawable( | ||
view = view, | ||
imagePrivacy = mappingContext.imagePrivacy, | ||
currentWireframeIndex = wireframes.size, | ||
x = contentXPosInDp, | ||
y = contentYPosInDp, | ||
width = contentWidthPx, | ||
height = contentHeightPx, | ||
usePIIPlaceholder = true, | ||
drawable = drawable, | ||
drawableCopier = drawableCopier, | ||
asyncJobStatusCallback = asyncJobStatusCallback, | ||
clipping = clipping, | ||
shapeStyle = null, | ||
border = null, | ||
prefix = "drawable", | ||
resourceIdCacheKey = generateUUID(view) | ||
)?.let { | ||
wireframes.add(it) | ||
} | ||
|
||
return wireframes | ||
} | ||
|
||
private fun generateUUID(reactImageView: ReactImageView): String { | ||
val source = reactImageView.imageSource?.source ?: | ||
System.identityHashCode(reactImageView).toString() | ||
val drawableType = reactImageView.drawable.current::class.java.name | ||
return "${drawableType}-${source}" | ||
} | ||
} | ||
|
Oops, something went wrong.