From 1ec890c8942b3f4030b455c4b8c18e5412a7e493 Mon Sep 17 00:00:00 2001 From: luyi Date: Thu, 26 Sep 2024 19:09:19 +0200 Subject: [PATCH] RUM-6189: Add Abstract and Text semantics mapper for compose SR --- detekt_custom.yml | 7 + .../api/dd-sdk-android-rum.api | 471 ++++++++++++++++++ .../compose/ComposeExtensionSupport.kt | 4 +- .../semantics/AbstractSemanticsNodeMapper.kt | 55 ++ .../mappers/semantics/SemanticsNodeMapper.kt | 19 + .../semantics/SemanticsWireframeMapper.kt | 96 ++++ .../semantics/TextSemanticsNodeMapper.kt | 148 ++++++ .../internal/reflection/ComposeReflection.kt | 11 + .../compose/internal/utils/SemanticsUtils.kt | 31 ++ .../compose/ComposeExtensionSupportTest.kt | 4 +- .../AbstractSemanticsNodeMapperTest.kt | 145 ++++++ .../semantics/SemanticsWireframeMapperTest.kt | 123 +++++ .../semantics/TextSemanticsNodeMapperTest.kt | 158 ++++++ .../elmyr/MappingContextForgeryFactory.kt | 24 + .../SessionReplayComposeForgeConfigurator.kt | 2 + .../elmyr/SystemInformationForgeryFactory.kt | 35 ++ 16 files changed, 1329 insertions(+), 4 deletions(-) create mode 100644 features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapper.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsNodeMapper.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapper.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapper.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapperTest.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapperTest.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapperTest.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/MappingContextForgeryFactory.kt create mode 100644 features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SystemInformationForgeryFactory.kt diff --git a/detekt_custom.yml b/detekt_custom.yml index ea3f612744..1bdd962fea 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -503,8 +503,11 @@ datadog: - "androidx.compose.ui.graphics.Color(kotlin.Long)" - "androidx.compose.ui.graphics.Color.toArgb()" - "androidx.compose.ui.layout.LayoutCoordinates.positionInWindow()" + - "androidx.compose.ui.layout.LayoutInfo.getModifierInfo()" - "androidx.compose.ui.unit.Density(kotlin.Float, kotlin.Float)" - "androidx.compose.ui.geometry.Size.copy(kotlin.Float, kotlin.Float)" + - "androidx.compose.ui.text.AnnotatedString.getStringAnnotations(kotlin.Int, kotlin.Int)" + - "androidx.compose.ui.semantics.SemanticsConfiguration.getOrNull(androidx.compose.ui.semantics.SemanticsPropertyKey)" - "androidx.core.view.GestureDetectorCompat.constructor(android.content.Context?, android.view.GestureDetector.OnGestureListener?)" - "androidx.core.view.GestureDetectorCompat.onTouchEvent(android.view.MotionEvent?)" - "androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks.onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?)" @@ -815,7 +818,9 @@ datadog: - "kotlin.collections.arrayListOf()" - "kotlin.collections.ArrayList(kotlin.collections.MutableCollection?)" - "kotlin.collections.Collection.flatten()" + - "kotlin.collections.Collection.forEach(kotlin.Function1)" - "kotlin.collections.Collection.isNotEmpty()" + - "kotlin.collections.listOf()" - "kotlin.collections.Collection.sumOf(kotlin.Function1)" - "kotlin.collections.Collection.withIndex()" - "kotlin.collections.HashMap()" @@ -959,6 +964,7 @@ datadog: - "kotlin.collections.MutableList.toSet()" - "kotlin.collections.MutableList.toTypedArray()" - "kotlin.collections.MutableList.withIndex()" + - "kotlin.collections.MutableList.firstOrNull()" - "kotlin.collections.MutableMap.clear()" - "kotlin.collections.MutableMap.containsKey(android.view.Window)" - "kotlin.collections.MutableMap.containsKey(com.datadog.android.api.SdkCore)" @@ -1125,6 +1131,7 @@ datadog: - "kotlin.Number.toFloat()" - "kotlin.Number.toLong()" - "kotlin.Short.toUShort()" + - "kotlin.ULong.toLong()" - "kotlin.UShort.toShort()" - "kotlin.math.abs(kotlin.Float)" - "kotlin.math.max(kotlin.Double, kotlin.Double)" diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 7dfe4e03b7..4f23aaa6db 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -6158,3 +6158,474 @@ public final class com/datadog/android/telemetry/model/TelemetryErrorEvent$View$ public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryErrorEvent$View; } +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun component2 ()J + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun component7 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun component8 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun component9 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public final fun getAction ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun getApplication ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun getDate ()J + public final fun getDd ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun getExperimentalFeatures ()Ljava/util/List; + public final fun getService ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun getSource ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun getTelemetry ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun getType ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Action { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Action$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Application { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Dd { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun getFormatVersion ()J + public final fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Device { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun getArchitecture ()Ljava/lang/String; + public final fun getBrand ()Ljava/lang/String; + public final fun getModel ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Device$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Os { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun getBuild ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Os$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Session { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field BROWSER Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source$Companion; + public static final field FLUTTER Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field IOS Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field UNITY Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static fun values ()[Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun component2 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun component3 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getDevice ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun getOs ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun getType ()Ljava/lang/String; + public final fun getUsage ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent : java/lang/Enum { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent$Companion; + public static final field GRANTED Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final field NOT_GRANTED Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final field PENDING Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static fun values ()[Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; +} + +public abstract class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Companion; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public abstract fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime$Companion; + public fun (ZZZ)V + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun copy (ZZZ)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime;ZZZILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public final fun getFeature ()Ljava/lang/String; + public final fun getNoActiveView ()Z + public final fun getNoView ()Z + public final fun getOverwritten ()Z + public fun hashCode ()I + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public final fun getFeature ()Ljava/lang/String; + public final fun getTrackingConsent ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public fun hashCode ()I + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public final fun getFeature ()Ljava/lang/String; + public fun hashCode ()I + public final fun isForced ()Ljava/lang/Boolean; + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$View { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; +} + diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupport.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupport.kt index 8c1e4a66b6..a33292578d 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupport.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupport.kt @@ -9,7 +9,7 @@ package com.datadog.android.sessionreplay.compose import androidx.compose.ui.platform.ComposeView import com.datadog.android.sessionreplay.ExtensionSupport import com.datadog.android.sessionreplay.MapperTypeWrapper -import com.datadog.android.sessionreplay.compose.internal.mappers.ComposeWireframeMapper +import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SemanticsWireframeMapper import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector import com.datadog.android.sessionreplay.utils.ColorStringFormatter import com.datadog.android.sessionreplay.utils.DefaultColorStringFormatter @@ -34,7 +34,7 @@ class ComposeExtensionSupport : ExtensionSupport { return listOf( MapperTypeWrapper( ComposeView::class.java, - ComposeWireframeMapper( + SemanticsWireframeMapper( viewIdentifierResolver, colorStringFormatter, viewBoundsResolver, diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapper.kt new file mode 100644 index 0000000000..c95855354f --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapper.kt @@ -0,0 +1,55 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.semantics.SemanticsNode +import com.datadog.android.sessionreplay.utils.ColorStringFormatter +import com.datadog.android.sessionreplay.utils.GlobalBounds +import kotlin.math.roundToInt + +internal abstract class AbstractSemanticsNodeMapper( + private val colorStringFormatter: ColorStringFormatter +) : SemanticsNodeMapper { + + protected fun resolveBound(semanticsNode: SemanticsNode): GlobalBounds { + val rect = semanticsNode.boundsInRoot + val density = semanticsNode.layoutInfo.density.density + val width = ((rect.right - rect.left) / density).toLong() + val height = ((rect.bottom - rect.top) / density).toLong() + val x = (rect.left / density).toLong() + val y = (rect.top / density).toLong() + return GlobalBounds(x, y, width, height) + } + + protected fun convertColor(color: Color): String { + return colorStringFormatter.formatColorAndAlphaAsHexString( + color.toArgb(), + (color.alpha * MAX_ALPHA).roundToInt() + ) + } + + protected fun convertColor(color: Long): String? { + return if (color == UNSPECIFIED_COLOR) { + null + } else { + val c = Color(color shr COMPOSE_COLOR_SHIFT) + colorStringFormatter.formatColorAndAlphaAsHexString( + c.toArgb(), + (c.alpha * MAX_ALPHA).roundToInt() + ) + } + } + + companion object { + /** As defined in Compose's ColorSpaces. */ + private const val UNSPECIFIED_COLOR = 16L + private const val COMPOSE_COLOR_SHIFT = 32 + private const val MAX_ALPHA = 255 + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsNodeMapper.kt new file mode 100644 index 0000000000..b4a947af05 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsNodeMapper.kt @@ -0,0 +1,19 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.semantics.SemanticsNode +import com.datadog.android.sessionreplay.compose.internal.data.ComposeWireframe +import com.datadog.android.sessionreplay.compose.internal.data.UiContext + +internal interface SemanticsNodeMapper { + + fun map( + semanticsNode: SemanticsNode, + parentContext: UiContext + ): ComposeWireframe? +} diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapper.kt new file mode 100644 index 0000000000..848ce74d19 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapper.kt @@ -0,0 +1,96 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import com.datadog.android.api.InternalLogger +import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.compose.internal.data.UiContext +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.model.MobileSegment +import com.datadog.android.sessionreplay.recorder.MappingContext +import com.datadog.android.sessionreplay.recorder.mapper.BaseWireframeMapper +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.ViewBoundsResolver +import com.datadog.android.sessionreplay.utils.ViewIdentifierResolver + +internal class SemanticsWireframeMapper( + viewIdentifierResolver: ViewIdentifierResolver, + colorStringFormatter: ColorStringFormatter, + viewBoundsResolver: ViewBoundsResolver, + drawableToColorMapper: DrawableToColorMapper, + private val semanticsUtils: SemanticsUtils = SemanticsUtils(), + private val semanticsNodeMapper: Map = mapOf( + // TODO RUM-6189 Add Mappers for each Semantics Role + ), + // Text doesn't have a role in semantics, so it should be a fallback mapper. + private val textSemanticsNodeMapper: TextSemanticsNodeMapper = TextSemanticsNodeMapper(colorStringFormatter) +) : BaseWireframeMapper( + viewIdentifierResolver, + colorStringFormatter, + viewBoundsResolver, + drawableToColorMapper +) { + override fun map( + view: ComposeView, + mappingContext: MappingContext, + asyncJobStatusCallback: AsyncJobStatusCallback, + internalLogger: InternalLogger + ): List { + val density = mappingContext.systemInformation.screenDensity.let { if (it == 0.0f) 1.0f else it } + val privacy = mappingContext.privacy + return semanticsUtils.findRootSemanticsNode(view)?.let { node -> + createComposeWireframes(node, density, privacy) + } ?: listOf() + } + + private fun getSemanticsNodeMapper( + semanticsNode: SemanticsNode + ): SemanticsNodeMapper { + val role = semanticsNode.config.getOrNull(SemanticsProperties.Role) + return semanticsNodeMapper[role] ?: textSemanticsNodeMapper + } + + private fun createComposeWireframes( + semanticsNode: SemanticsNode, + density: Float, + privacy: SessionReplayPrivacy + ): List { + val wireframes = mutableListOf() + createComposerWireframes( + semanticsNode = semanticsNode, + wireframes = wireframes, + parentUiContext = UiContext( + parentContentColor = null, + density = density, + privacy = privacy + ) + ) + return wireframes + } + + private fun createComposerWireframes( + semanticsNode: SemanticsNode, + wireframes: MutableList, + parentUiContext: UiContext + ) { + getSemanticsNodeMapper(semanticsNode) + .map(semanticsNode, parentUiContext)?.wireframe?.let { + wireframes.add(it) + } + val children = semanticsNode.children + children.forEach { + createComposerWireframes(it, wireframes, parentUiContext) + } + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapper.kt new file mode 100644 index 0000000000..a9452dc5ab --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapper.kt @@ -0,0 +1,148 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorProducer +import androidx.compose.ui.semantics.SemanticsActions +import androidx.compose.ui.semantics.SemanticsConfiguration +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.GenericFontFamily +import androidx.compose.ui.text.style.TextAlign +import com.datadog.android.sessionreplay.compose.internal.data.ComposeWireframe +import com.datadog.android.sessionreplay.compose.internal.data.UiContext +import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection +import com.datadog.android.sessionreplay.compose.internal.reflection.getSafe +import com.datadog.android.sessionreplay.model.MobileSegment +import com.datadog.android.sessionreplay.utils.ColorStringFormatter + +internal class TextSemanticsNodeMapper(colorStringFormatter: ColorStringFormatter) : + AbstractSemanticsNodeMapper(colorStringFormatter) { + override fun map(semanticsNode: SemanticsNode, parentContext: UiContext): ComposeWireframe { + val text = resolveText(semanticsNode.config) + val textStyle = resolveTextStyle(semanticsNode, parentContext) ?: defaultTextStyle + val bounds = resolveBound(semanticsNode) + return ComposeWireframe( + MobileSegment.Wireframe.TextWireframe( + id = semanticsNode.id.toLong(), + x = bounds.x, + y = bounds.y, + width = bounds.width, + height = bounds.height, + text = text ?: "", + textStyle = textStyle, + textPosition = resolveTextAlign(semanticsNode) + ), + null + ) + } + + private fun resolveTextAlign(semanticsNode: SemanticsNode): MobileSegment.TextPosition? { + return resolveSemanticsTextStyle(semanticsNode)?.let { + val align = when (it.textAlign) { + TextAlign.Start, + TextAlign.Left -> MobileSegment.Horizontal.LEFT + + TextAlign.End, + TextAlign.Right -> MobileSegment.Horizontal.RIGHT + + TextAlign.Justify, + TextAlign.Center -> MobileSegment.Horizontal.CENTER + + else -> MobileSegment.Horizontal.LEFT + } + MobileSegment.TextPosition( + alignment = MobileSegment.Alignment( + horizontal = align + ) + ) + } + } + + private fun resolveTextStyle(semanticsNode: SemanticsNode, parentContext: UiContext): MobileSegment.TextStyle? { + return resolveSemanticsTextStyle(semanticsNode)?.let { textStyle -> + val color = resolveModifierColor(semanticsNode) ?: textStyle.color + MobileSegment.TextStyle( + family = when (val value = textStyle.fontFamily) { + is GenericFontFamily -> value.name + else -> DEFAULT_FONT_FAMILY + }, + size = textStyle.fontSize.value.toLong(), + color = convertColor(color.value.toLong()) ?: parentContext.parentContentColor + ?: DEFAULT_TEXT_COLOR + ) + } + } + + private fun resolveModifierColor(semanticsNode: SemanticsNode): Color? { + val modifier = semanticsNode.layoutInfo.getModifierInfo().firstOrNull { + ComposeReflection.TextStringSimpleElement?.isInstance(it.modifier) ?: false + }?.modifier + modifier?.let { + if (ComposeReflection.TextStringSimpleElement?.isInstance(it) == true) { + val colorProducer = ComposeReflection.ColorProducerField?.getSafe(it) as? ColorProducer + return colorProducer?.invoke() + } + } + return null + } + + private fun resolveSemanticsTextStyle(semanticsNode: SemanticsNode): TextStyle? { + val textLayoutResults = mutableListOf() + semanticsNode.config.getOrNull(SemanticsActions.GetTextLayoutResult)?.action?.invoke(textLayoutResults) + return textLayoutResults.firstOrNull()?.layoutInput?.style + } + + private fun resolveText(semanticsConfiguration: SemanticsConfiguration): String? { + for ((key, value) in semanticsConfiguration) { + if (key.name == KEY_CONFIG_TEXT) { + return resolveAnnotatedString(value) + } + } + return null + } + + private fun resolveAnnotatedString(value: Any?): String { + return if (value is AnnotatedString) { + if (value.paragraphStyles.isEmpty() && + value.spanStyles.isEmpty() && + value.getStringAnnotations(0, value.text.length).isEmpty() + ) { + value.text + } else { + // Save space if we there is text only in the object + value.toString() + } + } else if (value is Collection<*>) { + val sb = StringBuilder() + value.forEach { + resolveAnnotatedString(it).let { + sb.append(it) + } + } + sb.toString() + } else { + value.toString() + } + } + + companion object { + private const val KEY_CONFIG_TEXT = "Text" + private const val DEFAULT_FONT_FAMILY = "Roboto, sans-serif" + private const val DEFAULT_TEXT_COLOR = "#000000FF" + private const val DEFAULT_FONT_SIZE = 12L + private val defaultTextStyle = MobileSegment.TextStyle( + size = DEFAULT_FONT_SIZE, + color = DEFAULT_TEXT_COLOR, + family = DEFAULT_FONT_FAMILY + ) + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt index 9f1192739f..8415298ce9 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt @@ -29,6 +29,17 @@ internal object ComposeReflection { val RecomposeScopeImplClass = getClassSafe("androidx.compose.runtime.RecomposeScopeImpl") val RecomposeScopeImplBlockField = RecomposeScopeImplClass?.getDeclaredFieldSafe("block") + + val AbstractComposeViewClass = getClassSafe("androidx.compose.ui.platform.AbstractComposeView") + val CompositionField = AbstractComposeViewClass?.getDeclaredFieldSafe("composition") + + val OwnerField = WrappedCompositionClass?.getDeclaredFieldSafe("owner") + + val AndroidComposeViewClass = getClassSafe("androidx.compose.ui.platform.AndroidComposeView") + val SemanticsOwner = AndroidComposeViewClass?.getDeclaredFieldSafe("semanticsOwner") + + val TextStringSimpleElement = getClassSafe("androidx.compose.foundation.text.modifiers.TextStringSimpleElement") + val ColorProducerField = TextStringSimpleElement?.getDeclaredFieldSafe("color") } internal fun Field.accessible(): Field { diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt new file mode 100644 index 0000000000..b7792534b7 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt @@ -0,0 +1,31 @@ +/* + * 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.compose.internal.utils + +import android.view.View +import androidx.compose.runtime.Composition +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsOwner +import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection +import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection.CompositionField +import com.datadog.android.sessionreplay.compose.internal.reflection.getSafe + +internal class SemanticsUtils { + + internal fun findRootSemanticsNode(view: View): SemanticsNode? { + val composition = CompositionField?.getSafe(view) as? Composition + if (ComposeReflection.WrappedCompositionClass?.isInstance(composition) == true) { + val owner = ComposeReflection.OwnerField?.getSafe(composition) + if (ComposeReflection.AndroidComposeViewClass?.isInstance(owner) == true) { + val semanticsOwner = ComposeReflection.SemanticsOwner?.getSafe(owner) as? SemanticsOwner + val rootNode = semanticsOwner?.unmergedRootSemanticsNode + return rootNode + } + } + return null + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupportTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupportTest.kt index be8e7e9963..c6b6496acb 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupportTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/ComposeExtensionSupportTest.kt @@ -8,7 +8,7 @@ package com.datadog.android.sessionreplay.compose import androidx.compose.ui.platform.ComposeView import com.datadog.android.sessionreplay.ExtensionSupport -import com.datadog.android.sessionreplay.compose.internal.mappers.ComposeWireframeMapper +import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SemanticsWireframeMapper import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -40,6 +40,6 @@ class ComposeExtensionSupportTest { // Then val composeMapper = customMappers.firstOrNull { it.supportsView(mockView) }?.getUnsafeMapper() - assertThat(composeMapper).isInstanceOf(ComposeWireframeMapper::class.java) + assertThat(composeMapper).isInstanceOf(SemanticsWireframeMapper::class.java) } } diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapperTest.kt new file mode 100644 index 0000000000..d2cd7d64a0 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/AbstractSemanticsNodeMapperTest.kt @@ -0,0 +1,145 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.layout.LayoutInfo +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.unit.Density +import com.datadog.android.sessionreplay.compose.internal.data.ComposeWireframe +import com.datadog.android.sessionreplay.compose.internal.data.UiContext +import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator +import com.datadog.android.sessionreplay.utils.ColorStringFormatter +import com.datadog.android.sessionreplay.utils.GlobalBounds +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.IntForgery +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.Mockito.mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import kotlin.math.roundToInt + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(SessionReplayComposeForgeConfigurator::class) +internal open class AbstractCompositionGroupMapperTest { + + private lateinit var testedMapper: StubAbstractSemanticsNodeMapper + + @Forgery + private lateinit var fakeWireframe: ComposeWireframe + + @IntForgery + var fakeSemanticsId: Int = 0 + + lateinit var fakeBounds: Rect + + @Mock + lateinit var mockColorStringFormatter: ColorStringFormatter + + @Mock + lateinit var mockLayoutInfo: LayoutInfo + + @Mock + lateinit var mockDensity: Density + + @FloatForgery + var fakeDensity = 0f + + @BeforeEach + open fun `set up`(forge: Forge) { + fakeBounds = Rect( + forge.aFloat(), + forge.aFloat(), + forge.aFloat(), + forge.aFloat() + ) + testedMapper = StubAbstractSemanticsNodeMapper(mockColorStringFormatter) + } + + @Test + fun `M return correct bound W resolveBound`() { + // Given + testedMapper.mappedWireframe = fakeWireframe + val mockNode = mockSemanticsNodeWithBound() + + // When + val result = testedMapper.resolveBounds(mockNode) + + // Then + assertThat(result.x).isEqualTo((fakeBounds.left / fakeDensity).toLong()) + assertThat(result.y).isEqualTo((fakeBounds.top / fakeDensity).toLong()) + assertThat(result.height).isEqualTo((fakeBounds.size.height / fakeDensity).toLong()) + assertThat(result.width).isEqualTo((fakeBounds.size.width / fakeDensity).toLong()) + } + + protected fun mockSemanticsNodeWithBound(additionalMock: SemanticsNode.() -> Unit = {}): SemanticsNode { + return mock().apply { + whenever(id) doReturn fakeSemanticsId + whenever(boundsInRoot) doReturn fakeBounds + whenever(layoutInfo) doReturn mockLayoutInfo + whenever(mockDensity.density) doReturn fakeDensity + whenever(layoutInfo.density) doReturn mockDensity + additionalMock.invoke(this) + } + } + + protected fun mockColorStringFormatter(color: Long, colorHexStr: String) { + val colorPair = convertColorIntAlpha(color) + whenever( + mockColorStringFormatter.formatColorAndAlphaAsHexString( + colorPair.first, + colorPair.second + ) + ).thenReturn(colorHexStr) + } + + private fun convertColorIntAlpha(color: Long): Pair { + val c = Color(color) + return Pair(c.toArgb(), (c.alpha * MAX_ALPHA).roundToInt()) + } + + companion object { + private const val MAX_ALPHA = 255 + } +} + +internal class StubAbstractSemanticsNodeMapper( + colorStringFormatter: ColorStringFormatter +) : AbstractSemanticsNodeMapper(colorStringFormatter) { + + var mappedWireframe: ComposeWireframe? = null + + override fun map(semanticsNode: SemanticsNode, parentContext: UiContext): ComposeWireframe? { + return null + } + + fun resolveBounds(semanticsNode: SemanticsNode): GlobalBounds { + return super.resolveBound(semanticsNode) + } + + fun covertColor(color: Long): String? { + return super.convertColor(color) + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapperTest.kt new file mode 100644 index 0000000000..c1a01eb89b --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SemanticsWireframeMapperTest.kt @@ -0,0 +1,123 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsConfiguration +import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import com.datadog.android.api.InternalLogger +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator +import com.datadog.android.sessionreplay.recorder.MappingContext +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.ViewBoundsResolver +import com.datadog.android.sessionreplay.utils.ViewIdentifierResolver +import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +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.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(SessionReplayComposeForgeConfigurator::class) +class SemanticsWireframeMapperTest { + + @Mock + private lateinit var mockTextSemanticsNodeMapper: TextSemanticsNodeMapper + + @Mock + private lateinit var mockViewIdentifierResolver: ViewIdentifierResolver + + @Mock + private lateinit var mockColorStringFormatter: ColorStringFormatter + + @Mock + private lateinit var mockViewBoundsResolver: ViewBoundsResolver + + @Mock + private lateinit var mockDrawableToColorMapper: DrawableToColorMapper + + @Mock + private lateinit var mockView: ComposeView + + @Mock + private lateinit var mockAsyncJobStatusCallback: AsyncJobStatusCallback + + @Mock + private lateinit var mockInternalLogger: InternalLogger + + @Mock + private lateinit var mockSemanticsUtils: SemanticsUtils + + @Mock + private lateinit var mockSemanticsConfiguration: SemanticsConfiguration + + @Forgery + private lateinit var fakeMappingContext: MappingContext + + private lateinit var testedSemanticsWireframeMapper: SemanticsWireframeMapper + + @BeforeEach + fun `set up`() { + testedSemanticsWireframeMapper = SemanticsWireframeMapper( + mockViewIdentifierResolver, + mockColorStringFormatter, + mockViewBoundsResolver, + mockDrawableToColorMapper, + mockSemanticsUtils, + mapOf(), + mockTextSemanticsNodeMapper + ) + } + + @Test + fun `M use TextSemanticsNodeMapper W not have role`() { + // Given + val mockSemanticsNode = mockSemanticsNode(null) + whenever(mockSemanticsUtils.findRootSemanticsNode(mockView)).thenReturn(mockSemanticsNode) + + // When + testedSemanticsWireframeMapper.map( + mockView, + fakeMappingContext, + mockAsyncJobStatusCallback, + mockInternalLogger + ) + + // Then + verify(mockTextSemanticsNodeMapper, times(1)).map(eq(mockSemanticsNode), any()) + } + + private fun mockSemanticsNode(role: Role?): SemanticsNode { + return mock { + whenever(mockSemanticsConfiguration.getOrNull(SemanticsProperties.Role)) doReturn role + whenever(it.config) doReturn mockSemanticsConfiguration + } + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapperTest.kt new file mode 100644 index 0000000000..0f0b22c6c1 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/TextSemanticsNodeMapperTest.kt @@ -0,0 +1,158 @@ +/* + * 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.compose.internal.mappers.semantics + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.AccessibilityAction +import androidx.compose.ui.semantics.SemanticsActions +import androidx.compose.ui.semantics.SemanticsConfiguration +import androidx.compose.ui.semantics.SemanticsPropertyKey +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.ui.text.TextLayoutInput +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.sp +import com.datadog.android.sessionreplay.compose.internal.data.UiContext +import com.datadog.android.sessionreplay.compose.internal.mappers.TextCompositionGroupMapper.Companion.DEFAULT_FONT_FAMILY +import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator +import com.datadog.android.sessionreplay.model.MobileSegment +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.Forgery +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.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(SessionReplayComposeForgeConfigurator::class) +internal class TextSemanticsNodeMapperTest : AbstractCompositionGroupMapperTest() { + + private lateinit var testedTextSemanticsNodeMapper: TextSemanticsNodeMapper + + @Mock + private lateinit var mockSemanticsConfiguration: SemanticsConfiguration + + @Mock + private lateinit var mockTextLayoutInput: TextLayoutInput + + @Mock + private lateinit var mockTextLayoutResult: TextLayoutResult + + @StringForgery(regex = "#[0-9A-F]{8}") + lateinit var fakeTextColorHexString: String + + private var stubTextLayoutResultAction: ((MutableList) -> Boolean) = { list -> + list.add(mockTextLayoutResult) + true + } + + var fakeTextAlign: TextAlign = TextAlign.Left + + @LongForgery(min = 0L, max = 0xffffff) + var fakeTextColor: Long = 0x12346778L + + @Forgery + lateinit var fakeUiContext: UiContext + + @StringForgery + lateinit var fakeText: String + + @IntForgery(min = 0, max = 100) + private var fakeFontSize = 0 + + @BeforeEach + override fun `set up`(forge: Forge) { + super.`set up`(forge) + fakeTextAlign = generateFakeTextAlign(forge = forge) + mockColorStringFormatter(fakeTextColor, fakeTextColorHexString) + + whenever(mockTextLayoutInput.style) doReturn TextStyle( + color = Color(fakeTextColor), + fontFamily = FontFamily.Default, + fontSize = fakeFontSize.sp, + textAlign = fakeTextAlign + ) + whenever(mockTextLayoutResult.layoutInput) doReturn mockTextLayoutInput + testedTextSemanticsNodeMapper = TextSemanticsNodeMapper(colorStringFormatter = mockColorStringFormatter) + } + + @Test + fun `M return the correct wireframe W map`() { + // Given + val map: Map, Any?> = mapOf(SemanticsPropertyKey(name = "Text") to fakeText) + val mockNode = mockSemanticsNodeWithBound { + whenever(mockSemanticsConfiguration.iterator()) doReturn map.iterator() + whenever(mockSemanticsConfiguration.getOrNull(SemanticsActions.GetTextLayoutResult)) doReturn + AccessibilityAction("", stubTextLayoutResultAction) + whenever(config) doReturn mockSemanticsConfiguration + } + val actual = testedTextSemanticsNodeMapper.map( + mockNode, + fakeUiContext + ) + + val expected = MobileSegment.Wireframe.TextWireframe( + id = fakeSemanticsId.toLong(), + x = (fakeBounds.left / fakeDensity).toLong(), + y = (fakeBounds.top / fakeDensity).toLong(), + width = (fakeBounds.size.width / fakeDensity).toLong(), + height = (fakeBounds.size.height / fakeDensity).toLong(), + text = fakeText, + textStyle = MobileSegment.TextStyle( + family = DEFAULT_FONT_FAMILY, + size = fakeFontSize.toLong(), + color = fakeTextColorHexString + ), + textPosition = MobileSegment.TextPosition( + alignment = resolveTextAlign(fakeTextAlign) + ) + ) + + assertThat(actual.wireframe).isEqualTo(expected) + } + + private fun resolveTextAlign(textAlign: TextAlign): MobileSegment.Alignment { + val align = when (textAlign) { + TextAlign.Start, + TextAlign.Left -> MobileSegment.Horizontal.LEFT + + TextAlign.End, + TextAlign.Right -> MobileSegment.Horizontal.RIGHT + + TextAlign.Justify, + TextAlign.Center -> MobileSegment.Horizontal.CENTER + + else -> MobileSegment.Horizontal.CENTER + } + return MobileSegment.Alignment( + horizontal = align + ) + } + + private fun generateFakeTextAlign(forge: Forge): TextAlign { + val index = forge.anInt(0, TextAlign.values().size) + return TextAlign.values()[index] + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/MappingContextForgeryFactory.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/MappingContextForgeryFactory.kt new file mode 100644 index 0000000000..403ea7e830 --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/MappingContextForgeryFactory.kt @@ -0,0 +1,24 @@ +/* + * 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.compose.test.elmyr + +import com.datadog.android.sessionreplay.recorder.MappingContext +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory +import org.mockito.Mockito.mock + +internal class MappingContextForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): MappingContext { + return MappingContext( + systemInformation = forge.getForgery(), + imageWireframeHelper = mock(), + hasOptionSelectorParent = forge.aBool(), + privacy = forge.getForgery(), + imagePrivacy = forge.getForgery() + ) + } +} diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SessionReplayComposeForgeConfigurator.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SessionReplayComposeForgeConfigurator.kt index 88e68a2509..a678e55701 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SessionReplayComposeForgeConfigurator.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SessionReplayComposeForgeConfigurator.kt @@ -31,5 +31,7 @@ class SessionReplayComposeForgeConfigurator : BaseConfigurator() { forge.addFactory(ComposeContextForgeryFactory()) forge.addFactory(UIContextForgeryFactory()) forge.addFactory(ComposeWireframeForgeryFactory()) + forge.addFactory(MappingContextForgeryFactory()) + forge.addFactory(SystemInformationForgeryFactory()) } } diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SystemInformationForgeryFactory.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SystemInformationForgeryFactory.kt new file mode 100644 index 0000000000..dfd4716cbc --- /dev/null +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/test/elmyr/SystemInformationForgeryFactory.kt @@ -0,0 +1,35 @@ +/* + * 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.compose.test.elmyr + +import android.content.res.Configuration +import com.datadog.android.sessionreplay.recorder.SystemInformation +import com.datadog.android.sessionreplay.utils.GlobalBounds +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class SystemInformationForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): SystemInformation { + return SystemInformation( + screenBounds = GlobalBounds( + x = 0, + y = 0, + width = forge.aPositiveLong(), + height = forge.aPositiveLong() + ), + screenOrientation = forge.anElementFrom( + intArrayOf( + Configuration.ORIENTATION_PORTRAIT, + Configuration.ORIENTATION_LANDSCAPE, + Configuration.ORIENTATION_UNDEFINED + ) + ), + screenDensity = forge.aFloat(min = 1f, max = 10f), + themeColor = forge.aNullable { aStringMatching("#[0-9A-Fa-f]{6}[fF]{2}") } + ) + } +}