@datadog/mobile-react-native-session-replay 2.4.3-alpha.0 → 2.5.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/DatadogSDKReactNativeSessionReplay.podspec +1 -1
  2. package/android/build.gradle +16 -6
  3. package/android/consumer-proguard-rules.pro +3 -0
  4. package/android/gradle.properties +1 -1
  5. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +34 -14
  6. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +15 -4
  7. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +8 -10
  8. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayPrivacySettings.kt +90 -0
  9. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +14 -0
  10. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +10 -0
  11. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/ReactDrawablesExt.kt +190 -0
  12. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactEditTextMapper.kt +84 -10
  13. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactNativeImageViewMapper.kt +106 -0
  14. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +4 -5
  15. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/resources/ReactDrawableCopier.kt +34 -0
  16. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +10 -23
  17. package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +28 -4
  18. package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +35 -3
  19. package/android/src/{main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt → rn75/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt} +6 -8
  20. package/android/src/rn75/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
  21. package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/extensions/LengthPercentageExt.kt +13 -0
  22. package/android/src/rn76/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +101 -0
  23. package/android/src/rnlegacy/kotlin/com.datadog.reactnative.sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +88 -0
  24. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +67 -9
  25. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +7 -3
  26. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +2 -7
  27. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +2 -7
  28. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +2 -1
  29. package/ios/Sources/DdSessionReplay.mm +46 -4
  30. package/ios/Sources/DdSessionReplayImplementation.swift +83 -11
  31. package/ios/Sources/RCTTextViewRecorder.swift +1 -1
  32. package/lib/commonjs/SessionReplay.js +78 -10
  33. package/lib/commonjs/SessionReplay.js.map +1 -1
  34. package/lib/commonjs/index.js +18 -0
  35. package/lib/commonjs/index.js.map +1 -1
  36. package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -1
  37. package/lib/module/SessionReplay.js +77 -9
  38. package/lib/module/SessionReplay.js.map +1 -1
  39. package/lib/module/index.js +2 -2
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/specs/NativeDdSessionReplay.js.map +1 -1
  42. package/lib/typescript/SessionReplay.d.ts +73 -4
  43. package/lib/typescript/SessionReplay.d.ts.map +1 -1
  44. package/lib/typescript/index.d.ts +2 -2
  45. package/lib/typescript/index.d.ts.map +1 -1
  46. package/lib/typescript/nativeModulesTypes.d.ts +18 -3
  47. package/lib/typescript/nativeModulesTypes.d.ts.map +1 -1
  48. package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -2
  49. package/lib/typescript/specs/NativeDdSessionReplay.d.ts.map +1 -1
  50. package/package.json +2 -1
  51. package/src/SessionReplay.ts +170 -23
  52. package/src/__tests__/SessionReplay.test.ts +94 -8
  53. package/src/index.ts +14 -2
  54. package/src/nativeModulesTypes.ts +27 -4
  55. package/src/specs/NativeDdSessionReplay.ts +21 -3
  56. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +0 -66
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  s.dependency "React-Core"
20
20
 
21
21
  # /!\ Remember to keep the version in sync with DatadogSDKReactNative.podspec
22
- s.dependency 'DatadogSessionReplay', '~> 2.14.1'
22
+ s.dependency 'DatadogSessionReplay', '~> 2.18.0'
23
23
  s.dependency 'DatadogSDKReactNative'
24
24
 
25
25
  s.test_spec 'Tests' do |test_spec|
@@ -12,7 +12,7 @@ buildscript {
12
12
  }
13
13
 
14
14
  dependencies {
15
- classpath 'com.android.tools.build:gradle:7.2.2'
15
+ classpath 'com.android.tools.build:gradle:7.3.1'
16
16
  // noinspection DifferentKotlinGradleVersion
17
17
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18
18
  classpath "org.jlleitschuh.gradle:ktlint-gradle:11.5.1"
@@ -96,15 +96,19 @@ def getExtOrIntegerDefault(name) {
96
96
  android {
97
97
  compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
98
98
  buildToolsVersion getExtOrDefault('buildToolsVersion')
99
+
99
100
  def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
101
+
100
102
  if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
101
103
  namespace = "com.datadog.reactnative.sessionreplay"
102
104
  }
105
+
103
106
  if (agpVersion.tokenize('.')[0].toInteger() >= 8) {
104
107
  buildFeatures {
105
108
  buildConfig = true
106
109
  }
107
110
  }
111
+
108
112
  if (agpVersion.tokenize('.')[0].toInteger() < 8) {
109
113
  compileOptions {
110
114
  sourceCompatibility JavaVersion.VERSION_11
@@ -130,6 +134,14 @@ android {
130
134
  } else {
131
135
  java.srcDirs += ['src/oldarch/kotlin']
132
136
  }
137
+
138
+ if (reactNativeMinorVersion >= 76) {
139
+ java.srcDirs += ['src/rn76/kotlin']
140
+ } else if (reactNativeMinorVersion >= 75) {
141
+ java.srcDirs += ['src/rn75/kotlin']
142
+ } else {
143
+ java.srcDirs += ['src/rnlegacy/kotlin']
144
+ }
133
145
  }
134
146
  test {
135
147
  java.srcDir("src/test/kotlin")
@@ -145,15 +157,12 @@ android {
145
157
  buildTypes {
146
158
  release {
147
159
  minifyEnabled false
160
+ consumerProguardFiles 'consumer-proguard-rules.pro'
148
161
  }
149
162
  }
150
163
  lintOptions {
151
164
  disable 'GradleCompatible'
152
165
  }
153
- compileOptions {
154
- sourceCompatibility JavaVersion.VERSION_11
155
- targetCompatibility JavaVersion.VERSION_11
156
- }
157
166
  }
158
167
 
159
168
  repositories {
@@ -188,7 +197,8 @@ dependencies {
188
197
  api "com.facebook.react:react-android:$reactNativeVersion"
189
198
  }
190
199
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
191
- implementation "com.datadoghq:dd-sdk-android-session-replay:2.11.0"
200
+ implementation "com.datadoghq:dd-sdk-android-session-replay:2.16.1"
201
+ implementation "com.datadoghq:dd-sdk-android-internal:2.16.1"
192
202
  implementation project(path: ':datadog_mobile-react-native')
193
203
 
194
204
  testImplementation "org.junit.platform:junit-platform-launcher:1.6.2"
@@ -0,0 +1,3 @@
1
+ -keepclassmembers class com.facebook.drawee.drawable.RoundedBitmapDrawable {
2
+ private android.graphics.Bitmap mBitmap;
3
+ }
@@ -1,4 +1,4 @@
1
- DatadogSDKReactNativeSessionReplay_kotlinVersion=1.7.21
1
+ DatadogSDKReactNativeSessionReplay_kotlinVersion=1.8.21
2
2
  DatadogSDKReactNativeSessionReplay_compileSdkVersion=33
3
3
  DatadogSDKReactNativeSessionReplay_buildToolsVersion=33.0.0
4
4
  DatadogSDKReactNativeSessionReplay_targetSdkVersion=33
@@ -6,10 +6,11 @@
6
6
 
7
7
  package com.datadog.reactnative.sessionreplay
8
8
 
9
- import com.datadog.android.Datadog
10
9
  import com.datadog.android.api.feature.FeatureSdkCore
10
+ import com.datadog.android.sessionreplay.ImagePrivacy
11
11
  import com.datadog.android.sessionreplay.SessionReplayConfiguration
12
- import com.datadog.android.sessionreplay.SessionReplayPrivacy
12
+ import com.datadog.android.sessionreplay.TextAndInputPrivacy
13
+ import com.datadog.android.sessionreplay.TouchPrivacy
13
14
  import com.datadog.reactnative.DatadogSDKWrapperStorage
14
15
  import com.facebook.react.bridge.Promise
15
16
  import com.facebook.react.bridge.ReactContext
@@ -27,14 +28,25 @@ class DdSessionReplayImplementation(
27
28
  /**
28
29
  * Enable session replay and start recording session.
29
30
  * @param replaySampleRate The sample rate applied for session replay.
30
- * @param defaultPrivacyLevel The privacy level used for replay.
31
31
  * @param customEndpoint Custom server url for sending replay data.
32
+ * @param privacySettings Defines the way visual elements should be masked.
33
+ * @param customEndpoint Custom server url for sending replay data.
34
+ * @param startRecordingImmediately Whether the recording should start immediately when the feature is enabled.
32
35
  */
33
- fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, customEndpoint: String, promise: Promise) {
36
+ fun enable(
37
+ replaySampleRate: Double,
38
+ customEndpoint: String,
39
+ privacySettings: SessionReplayPrivacySettings,
40
+ startRecordingImmediately: Boolean,
41
+ promise: Promise
42
+ ) {
34
43
  val sdkCore = DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
35
44
  val logger = sdkCore.internalLogger
36
45
  val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
37
- .setPrivacy(buildPrivacy(defaultPrivacyLevel))
46
+ .startRecordingImmediately(startRecordingImmediately)
47
+ .setImagePrivacy(privacySettings.imagePrivacyLevel)
48
+ .setTouchPrivacy(privacySettings.touchPrivacyLevel)
49
+ .setTextAndInputPrivacy(privacySettings.textAndInputPrivacyLevel)
38
50
  .addExtensionSupport(ReactNativeSessionReplayExtensionSupport(reactContext, logger))
39
51
 
40
52
  if (customEndpoint != "") {
@@ -45,16 +57,24 @@ class DdSessionReplayImplementation(
45
57
  promise.resolve(null)
46
58
  }
47
59
 
48
- private fun buildPrivacy(defaultPrivacyLevel: String): SessionReplayPrivacy {
49
- return when (defaultPrivacyLevel?.lowercase(Locale.US)) {
50
- "mask" -> SessionReplayPrivacy.MASK
51
- "mask_user_input" -> SessionReplayPrivacy.MASK_USER_INPUT
52
- "allow" -> SessionReplayPrivacy.ALLOW
53
- else -> {
54
- SessionReplayPrivacy.MASK
55
- }
56
- }
60
+ /**
61
+ * Manually start recording the current session.
62
+ */
63
+ fun startRecording(promise: Promise) {
64
+ sessionReplayProvider().startRecording(
65
+ DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
66
+ )
67
+ promise.resolve(null)
68
+ }
57
69
 
70
+ /**
71
+ * Manually stop recording the current session.
72
+ */
73
+ fun stopRecording(promise: Promise) {
74
+ sessionReplayProvider().stopRecording(
75
+ DatadogSDKWrapperStorage.getSdkCore() as FeatureSdkCore
76
+ )
77
+ promise.resolve(null)
58
78
  }
59
79
 
60
80
  companion object {
@@ -11,11 +11,14 @@ import com.datadog.android.api.InternalLogger
11
11
  import com.datadog.android.sessionreplay.ExtensionSupport
12
12
  import com.datadog.android.sessionreplay.MapperTypeWrapper
13
13
  import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
14
+ import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
14
15
  import com.datadog.reactnative.sessionreplay.mappers.ReactEditTextMapper
16
+ import com.datadog.reactnative.sessionreplay.mappers.ReactNativeImageViewMapper
15
17
  import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
16
18
  import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
17
19
  import com.facebook.react.bridge.ReactContext
18
20
  import com.facebook.react.uimanager.UIManagerModule
21
+ import com.facebook.react.views.image.ReactImageView
19
22
  import com.facebook.react.views.text.ReactTextView
20
23
  import com.facebook.react.views.textinput.ReactEditText
21
24
  import com.facebook.react.views.view.ReactViewGroup
@@ -24,21 +27,21 @@ internal class ReactNativeSessionReplayExtensionSupport(
24
27
  private val reactContext: ReactContext,
25
28
  private val logger: InternalLogger
26
29
  ) : ExtensionSupport {
30
+ override fun name(): String {
31
+ return ReactNativeSessionReplayExtensionSupport::class.java.simpleName
32
+ }
27
33
 
28
34
  override fun getCustomViewMappers(): List<MapperTypeWrapper<*>> {
29
35
  val uiManagerModule = getUiManagerModule()
30
36
 
31
37
  return listOf(
38
+ MapperTypeWrapper(ReactImageView::class.java, ReactNativeImageViewMapper()),
32
39
  MapperTypeWrapper(ReactViewGroup::class.java, ReactViewGroupMapper()),
33
40
  MapperTypeWrapper(ReactTextView::class.java, ReactTextMapper(reactContext, uiManagerModule)),
34
41
  MapperTypeWrapper(ReactEditText::class.java, ReactEditTextMapper(reactContext, uiManagerModule)),
35
42
  )
36
43
  }
37
44
 
38
- override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
39
- return listOf()
40
- }
41
-
42
45
  @VisibleForTesting
43
46
  internal fun getUiManagerModule(): UIManagerModule? {
44
47
  return try {
@@ -54,6 +57,14 @@ internal class ReactNativeSessionReplayExtensionSupport(
54
57
  }
55
58
  }
56
59
 
60
+ override fun getOptionSelectorDetectors(): List<OptionSelectorDetector> {
61
+ return listOf()
62
+ }
63
+
64
+ override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
65
+ return emptyList()
66
+ }
67
+
57
68
  internal companion object {
58
69
  internal const val RESOLVE_UIMANAGERMODULE_ERROR = "Unable to resolve UIManagerModule"
59
70
  }
@@ -6,28 +6,26 @@
6
6
 
7
7
  package com.datadog.reactnative.sessionreplay
8
8
 
9
+ import ReactViewBackgroundDrawableUtils
9
10
  import android.view.Gravity
10
11
  import android.widget.TextView
11
12
  import androidx.annotation.VisibleForTesting
13
+ import com.datadog.android.internal.utils.densityNormalized
12
14
  import com.datadog.android.sessionreplay.model.MobileSegment
13
- import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
14
15
  import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
15
- import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
16
16
  import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
17
17
  import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
18
18
  import com.facebook.react.bridge.ReactContext
19
19
  import com.facebook.react.uimanager.UIManagerModule
20
20
  import com.facebook.react.views.text.TextAttributes
21
- import com.facebook.react.views.view.ReactViewBackgroundDrawable
22
21
  import java.util.Locale
23
22
 
24
23
  internal class ReactTextPropertiesResolver(
25
24
  private val reactContext: ReactContext,
26
25
  private val uiManagerModule: UIManagerModule,
27
26
  private val reflectionUtils: ReflectionUtils = ReflectionUtils(),
28
- private val reactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils =
29
- ReactViewBackgroundDrawableUtils(),
30
- private val drawableUtils: DrawableUtils = DrawableUtils()
27
+ private val drawableUtils: DrawableUtils =
28
+ ReactViewBackgroundDrawableUtils()
31
29
  ): TextPropertiesResolver {
32
30
  override fun addReactNativeProperties(
33
31
  originalWireframe: MobileSegment.Wireframe.TextWireframe,
@@ -94,14 +92,14 @@ internal class ReactTextPropertiesResolver(
94
92
  view: TextView,
95
93
  pixelDensity: Float,
96
94
  ): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?>? {
97
- val backgroundDrawable: ReactViewBackgroundDrawable =
98
- drawableUtils.getReactBackgroundFromDrawable(view.background) ?: return null
95
+ val backgroundDrawable = drawableUtils
96
+ .getReactBackgroundFromDrawable(view.background) ?: return null
99
97
 
100
98
  // view.alpha is the value of the opacity prop on the js side
101
99
  val opacity = view.alpha
102
100
 
103
101
  val (shapeStyle, border) =
104
- reactViewBackgroundDrawableUtils
102
+ drawableUtils
105
103
  .resolveShapeAndBorder(backgroundDrawable, opacity, pixelDensity)
106
104
 
107
105
  return shapeStyle to border
@@ -136,7 +134,7 @@ internal class ReactTextPropertiesResolver(
136
134
  val fontFamily = getFontFamily(shadowNodeWrapper)
137
135
  ?: textWireframe.textStyle.family
138
136
  val fontSize = getFontSize(shadowNodeWrapper)
139
- ?.convertToDensityNormalized(pixelsDensity)
137
+ ?.densityNormalized(pixelsDensity)
140
138
  ?: textWireframe.textStyle.size
141
139
  val fontColor = getTextColor(shadowNodeWrapper)
142
140
  ?: textWireframe.textStyle.color
@@ -0,0 +1,90 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay
8
+
9
+ import android.util.Log
10
+ import com.datadog.android.sessionreplay.ImagePrivacy
11
+ import com.datadog.android.sessionreplay.TextAndInputPrivacy
12
+ import com.datadog.android.sessionreplay.TouchPrivacy
13
+
14
+ /**
15
+ * A utility class to store Session Replay privacy settings, and convert them from string to
16
+ * enum values.
17
+ *
18
+ * @param imagePrivacyLevel Defines the way images should be masked.
19
+ * @param touchPrivacyLevel Defines the way user touches should be masked.
20
+ * @param textAndInputPrivacyLevel Defines the way text and input should be masked.
21
+ */
22
+ class SessionReplayPrivacySettings(
23
+ imagePrivacyLevel: String,
24
+ touchPrivacyLevel: String,
25
+ textAndInputPrivacyLevel: String
26
+ ){
27
+ /**
28
+ * Defines the way images should be masked.
29
+ */
30
+ val imagePrivacyLevel = getImagePrivacy(imagePrivacyLevel)
31
+
32
+ /**
33
+ * Defines the way user touches should be masked.
34
+ */
35
+ val touchPrivacyLevel = getTouchPrivacy(touchPrivacyLevel)
36
+
37
+ /**
38
+ * Defines the way text and input should be masked.
39
+ */
40
+ val textAndInputPrivacyLevel = getTextAndInputPrivacy(textAndInputPrivacyLevel)
41
+
42
+ companion object {
43
+ internal fun getImagePrivacy(imagePrivacyLevel: String): ImagePrivacy {
44
+ return when (imagePrivacyLevel) {
45
+ "MASK_NON_BUNDLED_ONLY" -> ImagePrivacy.MASK_LARGE_ONLY
46
+ "MASK_ALL" -> ImagePrivacy.MASK_ALL
47
+ "MASK_NONE" -> ImagePrivacy.MASK_NONE
48
+ else -> {
49
+ Log.w(
50
+ SessionReplayPrivacySettings::class.java.canonicalName,
51
+ "Unknown Session Replay Image Privacy Level given: $imagePrivacyLevel, " +
52
+ "using ${ImagePrivacy.MASK_ALL} as default"
53
+ )
54
+ ImagePrivacy.MASK_ALL
55
+ }
56
+ }
57
+ }
58
+
59
+ internal fun getTouchPrivacy(touchPrivacyLevel: String): TouchPrivacy {
60
+ return when (touchPrivacyLevel) {
61
+ "SHOW" -> TouchPrivacy.SHOW
62
+ "HIDE" -> TouchPrivacy.HIDE
63
+ else -> {
64
+ Log.w(
65
+ SessionReplayPrivacySettings::class.java.canonicalName,
66
+ "Unknown Session Replay Touch Privacy Level given: $touchPrivacyLevel, " +
67
+ "using ${TouchPrivacy.HIDE} as default"
68
+ )
69
+ TouchPrivacy.HIDE
70
+ }
71
+ }
72
+ }
73
+
74
+ internal fun getTextAndInputPrivacy(textAndInputPrivacyLevel: String): TextAndInputPrivacy {
75
+ return when (textAndInputPrivacyLevel) {
76
+ "MASK_SENSITIVE_INPUTS" -> TextAndInputPrivacy.MASK_SENSITIVE_INPUTS
77
+ "MASK_ALL_INPUTS" -> TextAndInputPrivacy.MASK_ALL_INPUTS
78
+ "MASK_ALL" -> TextAndInputPrivacy.MASK_ALL
79
+ else -> {
80
+ Log.w(
81
+ SessionReplayPrivacySettings::class.java.canonicalName,
82
+ "Unknown Session Replay Text And Input Privacy Level given: $textAndInputPrivacyLevel, " +
83
+ "using ${TextAndInputPrivacy.MASK_ALL} as default"
84
+ )
85
+ TextAndInputPrivacy.MASK_ALL
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
@@ -24,4 +24,18 @@ internal class SessionReplaySDKWrapper : SessionReplayWrapper {
24
24
  sdkCore,
25
25
  )
26
26
  }
27
+
28
+ /**
29
+ * Manually start recording the current session.
30
+ */
31
+ override fun startRecording(sdkCore: SdkCore) {
32
+ SessionReplay.startRecording(sdkCore)
33
+ }
34
+
35
+ /**
36
+ * Manually stop recording the current session.
37
+ */
38
+ override fun stopRecording(sdkCore: SdkCore) {
39
+ SessionReplay.stopRecording(sdkCore)
40
+ }
27
41
  }
@@ -21,4 +21,14 @@ interface SessionReplayWrapper {
21
21
  sessionReplayConfiguration: SessionReplayConfiguration,
22
22
  sdkCore: SdkCore
23
23
  )
24
+
25
+ /**
26
+ * Manually start recording the current session.
27
+ */
28
+ fun startRecording(sdkCore: SdkCore)
29
+
30
+ /**
31
+ * Manually stop recording the current session.
32
+ */
33
+ fun stopRecording(sdkCore: SdkCore)
24
34
  }
@@ -0,0 +1,190 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.extensions
8
+
9
+ import android.content.res.Resources
10
+ import android.graphics.Bitmap
11
+ import android.graphics.Bitmap.Config
12
+ import android.graphics.Canvas
13
+ import android.graphics.drawable.BitmapDrawable
14
+ import android.graphics.drawable.Drawable
15
+ import android.graphics.drawable.ShapeDrawable
16
+ import android.graphics.drawable.VectorDrawable
17
+ import android.widget.ImageView
18
+ import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
19
+ import com.facebook.drawee.drawable.ArrayDrawable
20
+ import com.facebook.drawee.drawable.ForwardingDrawable
21
+ import com.facebook.drawee.drawable.RoundedBitmapDrawable
22
+ import com.facebook.drawee.drawable.ScaleTypeDrawable
23
+ import com.facebook.drawee.drawable.ScalingUtils
24
+
25
+ internal fun ScaleTypeDrawable.imageViewScaleType(): ImageView.ScaleType? {
26
+ return when (scaleType) {
27
+ ScalingUtils.ScaleType.CENTER -> ImageView.ScaleType.CENTER
28
+ ScalingUtils.ScaleType.CENTER_CROP -> ImageView.ScaleType.CENTER_CROP
29
+ ScalingUtils.ScaleType.CENTER_INSIDE -> ImageView.ScaleType.CENTER_INSIDE
30
+ ScalingUtils.ScaleType.FIT_CENTER -> ImageView.ScaleType.FIT_CENTER
31
+ ScalingUtils.ScaleType.FIT_START -> ImageView.ScaleType.FIT_START
32
+ ScalingUtils.ScaleType.FIT_END -> ImageView.ScaleType.FIT_END
33
+ ScalingUtils.ScaleType.FIT_XY -> ImageView.ScaleType.FIT_XY
34
+ else -> null
35
+ }
36
+ }
37
+
38
+ internal fun ArrayDrawable.getScaleTypeDrawable(): ScaleTypeDrawable? {
39
+ for (i in 0 until numberOfLayers) {
40
+ val drawable = getDrawableOrNull(i)
41
+ if (drawable is ScaleTypeDrawable) return drawable
42
+ }
43
+
44
+ return null
45
+ }
46
+
47
+ internal fun ArrayDrawable.getDrawableOrNull(index: Int): Drawable? {
48
+ return try {
49
+ getDrawable(index)
50
+ } catch (_: IllegalArgumentException) {
51
+ null
52
+ }
53
+ }
54
+
55
+ internal fun ForwardingDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
56
+ val forwardedDrawable = drawable
57
+ return if (forwardedDrawable != null) {
58
+ forwardedDrawable.tryToExtractBitmap(resources)
59
+ } else {
60
+ toBitmapOrNull(
61
+ intrinsicWidth,
62
+ intrinsicHeight,
63
+ Config.ARGB_8888
64
+ )
65
+ }
66
+ }
67
+
68
+ internal fun RoundedBitmapDrawable.tryToExtractBitmap(): Bitmap? {
69
+ val privateBitmap = try {
70
+ val field = RoundedBitmapDrawable::class.java.getDeclaredField("mBitmap")
71
+ field.isAccessible = true
72
+ field.get(this) as? Bitmap
73
+ } catch (_: NoSuchFieldException) {
74
+ null
75
+ } catch (_: IllegalAccessException) {
76
+ null
77
+ } catch (_: Exception) {
78
+ null
79
+ }
80
+
81
+ return privateBitmap ?: toBitmapOrNull(
82
+ intrinsicWidth,
83
+ intrinsicHeight,
84
+ Config.ARGB_8888
85
+ )
86
+ }
87
+
88
+ internal fun BitmapDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
89
+ if (bitmap != null) {
90
+ return bitmap
91
+ }
92
+
93
+ if (constantState != null) {
94
+ val copy = constantState?.newDrawable(resources)
95
+ return (copy as? BitmapDrawable)?.bitmap ?: copy?.toBitmapOrNull(
96
+ intrinsicWidth,
97
+ intrinsicHeight,
98
+ Config.ARGB_8888
99
+ )
100
+ }
101
+
102
+ return null
103
+ }
104
+
105
+ internal fun ArrayDrawable.tryToExtractBitmap(resources: Resources): Bitmap? {
106
+ var width = 0
107
+ var height = 0
108
+ for (index in 0 until numberOfLayers) {
109
+ val drawable = getDrawableOrNull(index) ?: continue
110
+
111
+ if (drawable is ScaleTypeDrawable) {
112
+ return drawable.tryToExtractBitmap(resources)
113
+ }
114
+
115
+ if (drawable.intrinsicWidth * drawable.intrinsicHeight > width * height) {
116
+ width = drawable.intrinsicWidth
117
+ height = drawable.intrinsicHeight
118
+ }
119
+ }
120
+
121
+ return if (width > 0 && height > 0)
122
+ toBitmapOrNull(width, height, Config.ARGB_8888)
123
+ else
124
+ null
125
+ }
126
+
127
+ internal fun Drawable.tryToExtractBitmap(
128
+ resources: Resources
129
+ ): Bitmap? {
130
+ when (this) {
131
+ is ArrayDrawable -> {
132
+ return tryToExtractBitmap(resources)
133
+ }
134
+ is ForwardingDrawable -> {
135
+ return tryToExtractBitmap(resources)
136
+ }
137
+ is RoundedBitmapDrawable -> {
138
+ return tryToExtractBitmap()
139
+ }
140
+ is BitmapDrawable -> {
141
+ return tryToExtractBitmap(resources)
142
+ }
143
+ is VectorDrawable, is ShapeDrawable, is DrawerArrowDrawable -> {
144
+ return toBitmapOrNull(
145
+ intrinsicWidth,
146
+ intrinsicHeight,
147
+ Config.ARGB_8888
148
+ )
149
+ }
150
+ else -> return null
151
+ }
152
+ }
153
+
154
+ internal fun Drawable.toBitmapOrNull(
155
+ width: Int = intrinsicWidth,
156
+ height: Int = intrinsicHeight,
157
+ config: Config? = null
158
+ ): Bitmap? {
159
+ if (this is BitmapDrawable && bitmap == null) {
160
+ return null
161
+ }
162
+ return toBitmap(width, height, config)
163
+ }
164
+
165
+ internal fun Drawable.toBitmap(
166
+ width: Int = intrinsicWidth,
167
+ height: Int = intrinsicHeight,
168
+ config: Config? = null
169
+ ): Bitmap {
170
+ if (this is BitmapDrawable) {
171
+ if (bitmap == null) {
172
+ return Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
173
+ }
174
+ if (config == null || bitmap.config == config) {
175
+ // Fast-path to return original. Bitmap.createScaledBitmap will do this check, but it
176
+ // involves allocation and two jumps into native code so we perform the check ourselves.
177
+ if (width == bitmap.width && height == bitmap.height) {
178
+ return bitmap
179
+ }
180
+ return Bitmap.createScaledBitmap(bitmap, width, height, true)
181
+ }
182
+ }
183
+
184
+ val bitmap = Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
185
+ setBounds(0, 0, width, height)
186
+ draw(Canvas(bitmap))
187
+
188
+ setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom)
189
+ return bitmap
190
+ }