Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUM-6897: Fix scrolling issue & Add DrawableCopier interface for drawable copying for image wireframe #2354

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ enum com.datadog.android.sessionreplay.TextAndInputPrivacy : PrivacyLevel
enum com.datadog.android.sessionreplay.TouchPrivacy : PrivacyLevel
- SHOW
- HIDE
class com.datadog.android.sessionreplay.internal.recorder.resources.DefaultDrawableCopier : DrawableCopier
override fun copy(android.graphics.drawable.Drawable, android.content.res.Resources): android.graphics.drawable.Drawable?
interface com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier
fun copy(android.graphics.drawable.Drawable, android.content.res.Resources): android.graphics.drawable.Drawable?
data class com.datadog.android.sessionreplay.recorder.MappingContext
constructor(SystemInformation, com.datadog.android.sessionreplay.utils.ImageWireframeHelper, com.datadog.android.sessionreplay.TextAndInputPrivacy, com.datadog.android.sessionreplay.ImagePrivacy, Boolean = false)
interface com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
Expand Down Expand Up @@ -106,7 +110,7 @@ interface com.datadog.android.sessionreplay.utils.DrawableToColorMapper
data class com.datadog.android.sessionreplay.utils.GlobalBounds
constructor(Long, Long, Long, Long)
interface com.datadog.android.sessionreplay.utils.ImageWireframeHelper
fun createImageWireframe(android.view.View, com.datadog.android.sessionreplay.ImagePrivacy, Int, Long, Long, Int, Int, Boolean, android.graphics.drawable.Drawable, AsyncJobStatusCallback, com.datadog.android.sessionreplay.model.MobileSegment.WireframeClip? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeBorder? = null, String? = DRAWABLE_CHILD_NAME): com.datadog.android.sessionreplay.model.MobileSegment.Wireframe?
fun createImageWireframe(android.view.View, com.datadog.android.sessionreplay.ImagePrivacy, Int, Long, Long, Int, Int, Boolean, android.graphics.drawable.Drawable, com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier = DefaultDrawableCopier(), AsyncJobStatusCallback, com.datadog.android.sessionreplay.model.MobileSegment.WireframeClip? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeBorder? = null, String? = DRAWABLE_CHILD_NAME): com.datadog.android.sessionreplay.model.MobileSegment.Wireframe?
fun createCompoundDrawableWireframes(android.widget.TextView, com.datadog.android.sessionreplay.recorder.MappingContext, Int, AsyncJobStatusCallback): MutableList<com.datadog.android.sessionreplay.model.MobileSegment.Wireframe>
companion object
open class com.datadog.android.sessionreplay.utils.LegacyDrawableToColorMapper : DrawableToColorMapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/En
public static fun values ()[Lcom/datadog/android/sessionreplay/TouchPrivacy;
}

public final class com/datadog/android/sessionreplay/internal/recorder/resources/DefaultDrawableCopier : com/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier {
public fun <init> ()V
public fun copy (Landroid/graphics/drawable/Drawable;Landroid/content/res/Resources;)Landroid/graphics/drawable/Drawable;
}

public abstract interface class com/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier {
public abstract fun copy (Landroid/graphics/drawable/Drawable;Landroid/content/res/Resources;)Landroid/graphics/drawable/Drawable;
}

public final class com/datadog/android/sessionreplay/model/MobileSegment {
public static final field Companion Lcom/datadog/android/sessionreplay/model/MobileSegment$Companion;
public fun <init> (Lcom/datadog/android/sessionreplay/model/MobileSegment$Application;Lcom/datadog/android/sessionreplay/model/MobileSegment$Session;Lcom/datadog/android/sessionreplay/model/MobileSegment$View;JJJLjava/lang/Long;Ljava/lang/Boolean;Lcom/datadog/android/sessionreplay/model/MobileSegment$Source;Ljava/util/List;)V
Expand Down Expand Up @@ -1553,14 +1562,14 @@ public final class com/datadog/android/sessionreplay/utils/GlobalBounds {
public abstract interface class com/datadog/android/sessionreplay/utils/ImageWireframeHelper {
public static final field Companion Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper$Companion;
public abstract fun createCompoundDrawableWireframes (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/recorder/MappingContext;ILcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;)Ljava/util/List;
public abstract fun createImageWireframe (Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
public abstract fun createImageWireframe (Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
}

public final class com/datadog/android/sessionreplay/utils/ImageWireframeHelper$Companion {
}

public final class com/datadog/android/sessionreplay/utils/ImageWireframeHelper$DefaultImpls {
public static synthetic fun createImageWireframe$default (Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
public static synthetic fun createImageWireframe$default (Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
}

public class com/datadog/android/sessionreplay/utils/LegacyDrawableToColorMapper : com/datadog/android/sessionreplay/utils/DrawableToColorMapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,25 @@ internal class ImageViewMapper(
val contentYPosInDp = contentRect.top.densityNormalized(density).toLong()
val contentWidthPx = contentRect.width()
val contentHeightPx = contentRect.height()
val contentDrawable = drawable.constantState?.newDrawable(resources)

if (contentDrawable != null) {
// resolve foreground
mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mappingContext.imagePrivacy,
currentWireframeIndex = wireframes.size,
x = contentXPosInDp,
y = contentYPosInDp,
width = contentWidthPx,
height = contentHeightPx,
usePIIPlaceholder = true,
drawable = contentDrawable,
asyncJobStatusCallback = asyncJobStatusCallback,
clipping = clipping,
shapeStyle = null,
border = null,
prefix = ImageWireframeHelper.DRAWABLE_CHILD_NAME
)?.let {
wireframes.add(it)
}
// resolve foreground
mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mappingContext.imagePrivacy,
currentWireframeIndex = wireframes.size,
x = contentXPosInDp,
y = contentYPosInDp,
width = contentWidthPx,
height = contentHeightPx,
usePIIPlaceholder = true,
drawable = drawable,
asyncJobStatusCallback = asyncJobStatusCallback,
clipping = clipping,
shapeStyle = null,
border = null,
prefix = ImageWireframeHelper.DRAWABLE_CHILD_NAME
)?.let {
wireframes.add(it)
}

return wireframes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@

package com.datadog.android.sessionreplay.internal.recorder.mapper

import android.content.res.Resources
import android.graphics.drawable.Drawable
import androidx.annotation.UiThread
import androidx.appcompat.widget.SwitchCompat
import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.internal.recorder.densityNormalized
import com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier
import com.datadog.android.sessionreplay.model.MobileSegment
import com.datadog.android.sessionreplay.recorder.MappingContext
import com.datadog.android.sessionreplay.recorder.mapper.TextViewMapper
Expand Down Expand Up @@ -71,28 +74,35 @@ internal open class SwitchCompatMapper(
mappingContext.systemInformation.screenDensity
)
return trackBounds?.let {
return view.trackDrawable.constantState?.newDrawable(view.resources)?.apply {
setState(view.trackDrawable.state)
bounds = view.trackDrawable.bounds
view.trackTintList?.let {
setTintList(it)
val trackDrawable = view.trackDrawable
val drawableCopier = object : DrawableCopier {
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
return originalDrawable.constantState?.newDrawable(view.resources)?.apply {
setState(view.trackDrawable.state)
bounds = view.trackDrawable.bounds
view.trackTintList?.let {
setTintList(it)
}
}
}
}?.let { drawable ->
mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
currentWireframeIndex = prevIndex + 1,
x = trackBounds.x.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
y = trackBounds.y.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
width = trackBounds.width,
height = trackBounds.height,
drawable = drawable,
shapeStyle = null,
border = null,
usePIIPlaceholder = true,
asyncJobStatusCallback = asyncJobStatusCallback
)
}
return mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
currentWireframeIndex = prevIndex + 1,
x = it.x.densityNormalized(mappingContext.systemInformation.screenDensity)
.toLong(),
y = it.y.densityNormalized(mappingContext.systemInformation.screenDensity)
.toLong(),
width = it.width,
height = it.height,
drawable = trackDrawable,
drawableCopier = drawableCopier,
shapeStyle = null,
border = null,
usePIIPlaceholder = true,
asyncJobStatusCallback = asyncJobStatusCallback
)
}
}

Expand All @@ -107,25 +117,38 @@ internal open class SwitchCompatMapper(
mappingContext.systemInformation.screenDensity
)

return view.thumbDrawable?.let { drawable ->
thumbBounds?.let { thumbBounds ->
mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
currentWireframeIndex = prevIndex + 1,
x = thumbBounds.x.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
y = thumbBounds.y.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
width = drawable.intrinsicWidth,
height = drawable.intrinsicHeight,
drawable = drawable,
shapeStyle = null,
border = null,
usePIIPlaceholder = true,
clipping = null,
asyncJobStatusCallback = asyncJobStatusCallback
)
val thumbDrawable = view.thumbDrawable
val drawableCopier = object : DrawableCopier {
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
return originalDrawable.constantState?.newDrawable(view.resources)?.apply {
setState(view.thumbDrawable.state)
bounds = view.thumbDrawable.bounds
view.thumbTintList?.let {
setTintList(it)
}
}
}
}
return thumbBounds?.let {
mappingContext.imageWireframeHelper.createImageWireframe(
view = view,
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
currentWireframeIndex = prevIndex + 1,
x = it.x.densityNormalized(mappingContext.systemInformation.screenDensity)
.toLong(),
y = it.y.densityNormalized(mappingContext.systemInformation.screenDensity)
.toLong(),
width = thumbDrawable.intrinsicWidth,
height = thumbDrawable.intrinsicHeight,
drawable = thumbDrawable,
drawableCopier = drawableCopier,
shapeStyle = null,
border = null,
usePIIPlaceholder = true,
clipping = null,
asyncJobStatusCallback = asyncJobStatusCallback
)
}
}

private fun resolveThumbBounds(view: SwitchCompat, pixelsDensity: Float): GlobalBoundsInPx? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.resources

import android.content.res.Resources
import android.graphics.drawable.Drawable
import com.datadog.android.lint.InternalApi

/**
* Default implementation of [DrawableCopier] interface, it copies the drawable from constant state.
*/
@InternalApi
class DefaultDrawableCopier : DrawableCopier {
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
return originalDrawable.constantState?.newDrawable(resources)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal class DefaultImageWireframeHelper(
height: Int,
usePIIPlaceholder: Boolean,
drawable: Drawable,
drawableCopier: DrawableCopier,
asyncJobStatusCallback: AsyncJobStatusCallback,
clipping: MobileSegment.WireframeClip?,
shapeStyle: MobileSegment.ShapeStyle?,
Expand Down Expand Up @@ -137,7 +138,8 @@ internal class DefaultImageWireframeHelper(
resources = resources,
applicationContext = applicationContext,
displayMetrics = displayMetrics,
drawable = drawableProperties.drawable,
originalDrawable = drawableProperties.drawable,
drawableCopier = drawableCopier,
drawableWidth = width,
drawableHeight = height,
resourceResolverCallback = object : ResourceResolverCallback {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.resources

import android.content.res.Resources
import android.graphics.drawable.Drawable
import com.datadog.android.lint.InternalApi

/**
* Interface of copying drawable to a new one.
*/
@InternalApi
interface DrawableCopier {

/**
* Called to copy the drawable.
* @param originalDrawable the original drawable to copy
* @param resources resources of the view.
*
* @return New copied drawable.
ambushwork marked this conversation as resolved.
Show resolved Hide resolved
*/
fun copy(originalDrawable: Drawable, resources: Resources): Drawable?
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ internal class ResourceResolver(
resources: Resources,
applicationContext: Context,
displayMetrics: DisplayMetrics,
drawable: Drawable,
originalDrawable: Drawable,
drawableCopier: DrawableCopier,
drawableWidth: Int,
drawableHeight: Int,
resourceResolverCallback: ResourceResolverCallback
) {
bitmapCachesManager.registerCallbacks(applicationContext)

val resourceId = tryToGetResourceFromCache(drawable = drawable)
val resourceId = tryToGetResourceFromCache(drawable = originalDrawable)

if (resourceId != null) {
// if we got here it means we saw the bitmap before,
Expand All @@ -61,9 +62,11 @@ internal class ResourceResolver(
return
}

val copiedDrawable = drawableCopier.copy(originalDrawable, resources) ?: return

val bitmapFromDrawable =
if (drawable is BitmapDrawable && shouldUseDrawableBitmap(drawable)) {
drawable.bitmap // cannot be null - we already checked in shouldUseDrawableBitmap
if (copiedDrawable is BitmapDrawable && shouldUseDrawableBitmap(copiedDrawable)) {
copiedDrawable.bitmap // cannot be null - we already checked in shouldUseDrawableBitmap
} else {
null
}
Expand All @@ -72,7 +75,7 @@ internal class ResourceResolver(
threadPoolExecutor.executeSafe("resolveResourceId", logger) {
createBitmap(
resources = resources,
drawable = drawable,
drawable = originalDrawable,
drawableWidth = drawableWidth,
drawableHeight = drawableHeight,
displayMetrics = displayMetrics,
Expand Down
Loading