()V
+ public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
+}
+
+public final class io/embrace/android/embracesdk/okhttp3/swazzle/callback/okhttp3/OkHttpClient {
+}
+
+public final class io/embrace/android/embracesdk/okhttp3/swazzle/callback/okhttp3/OkHttpClient$Builder {
+ public static fun _constructorOnPostBody (Lokhttp3/OkHttpClient$Builder;)V
+ public static fun _preBuild (Lokhttp3/OkHttpClient$Builder;)V
+}
+
diff --git a/embrace-android-okhttp3/build.gradle b/embrace-android-okhttp3/build.gradle
new file mode 100644
index 0000000000..a806e5c640
--- /dev/null
+++ b/embrace-android-okhttp3/build.gradle
@@ -0,0 +1,17 @@
+plugins {
+ id("internal-embrace-plugin")
+}
+
+description = "Embrace Android SDK: OkHttp3"
+
+android {
+ namespace = "io.embrace.android.embracesdk.okhttp3"
+}
+
+dependencies {
+ compileOnly("com.squareup.okhttp3:okhttp:4.9.3")
+ compileOnly(project(":embrace-android-sdk"))
+ testImplementation(project(":embrace-android-sdk"))
+ testImplementation "io.mockk:mockk:1.12.2"
+ testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
+}
diff --git a/embrace-android-okhttp3/lint-baseline.xml b/embrace-android-okhttp3/lint-baseline.xml
new file mode 100644
index 0000000000..794b7879ee
--- /dev/null
+++ b/embrace-android-okhttp3/lint-baseline.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embrace-android-okhttp3/src/main/AndroidManifest.xml b/embrace-android-okhttp3/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9733c38819
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceCustomPathException.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceCustomPathException.java
new file mode 100644
index 0000000000..93ebdf2902
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceCustomPathException.java
@@ -0,0 +1,24 @@
+package io.embrace.android.embracesdk.okhttp3;
+
+import java.io.IOException;
+
+import io.embrace.android.embracesdk.InternalApi;
+
+/**
+ * We use the EmbraceCustomPathException to capture the custom path added in the
+ * intercept chain process for client errors.
+ */
+@InternalApi
+public class EmbraceCustomPathException extends IOException {
+
+ private final String customPath;
+
+ public EmbraceCustomPathException(String customPath, Throwable cause) {
+ super(cause);
+ this.customPath = customPath;
+ }
+
+ public String getCustomPath() {
+ return customPath;
+ }
+}
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3ApplicationInterceptor.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3ApplicationInterceptor.java
new file mode 100644
index 0000000000..37f578e0cc
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3ApplicationInterceptor.java
@@ -0,0 +1,102 @@
+package io.embrace.android.embracesdk.okhttp3;
+
+import static io.embrace.android.embracesdk.config.behavior.NetworkSpanForwardingBehavior.TRACEPARENT_HEADER_NAME;
+import static io.embrace.android.embracesdk.internal.utils.ThrowableUtilsKt.causeMessage;
+import static io.embrace.android.embracesdk.internal.utils.ThrowableUtilsKt.causeName;
+
+import java.io.IOException;
+
+import io.embrace.android.embracesdk.Embrace;
+import io.embrace.android.embracesdk.InternalApi;
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest;
+import io.embrace.android.embracesdk.network.http.EmbraceHttpPathOverride;
+import io.embrace.android.embracesdk.network.http.HttpMethod;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * This interceptor will only intercept errors that client app experiences.
+ *
+ * We used OkHttp3 application interceptor in this case because this interceptor
+ * will be added first in the OkHttp3 interceptors stack. This allows us to catch network errors.
+ * OkHttp3 network interceptors are added almost at the end of stack, they are closer to "Wire"
+ * so they are not able to see network errors.
+ *
+ * Application interceptors: - Don't need to worry about intermediate responses like
+ * redirects and retries. - Are always invoked once, even if the HTTP response is served
+ * from the cache. - Observe the application's original intent. Unconcerned with OkHttp-injected
+ * headers like If-None-Match. - Permitted to short-circuit and not call
+ * Chain.proceed(). - Permitted to retry and make multiple calls to Chain.proceed().
+ *
+ * We used the EmbraceGraphQLException to capture the custom path added in the intercept
+ * chain process for client errors on graphql requests.
+ */
+@InternalApi
+public class EmbraceOkHttp3ApplicationInterceptor implements Interceptor {
+ static final String UNKNOWN_EXCEPTION = "Unknown";
+ static final String UNKNOWN_MESSAGE = "An error occurred during the execution of this network request";
+ final Embrace embrace;
+
+ private final SdkFacade sdkFacade;
+
+ public EmbraceOkHttp3ApplicationInterceptor() {
+ this(Embrace.getInstance(), new SdkFacade());
+ }
+
+ EmbraceOkHttp3ApplicationInterceptor(Embrace embrace, SdkFacade sdkFacade) {
+ this.embrace = embrace;
+ this.sdkFacade = sdkFacade;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ long startTime = System.currentTimeMillis();
+ Request request = chain.request();
+ try {
+ // we are not interested in response, just proceed
+ return chain.proceed(request);
+ } catch (EmbraceCustomPathException e) {
+ if (embrace.isStarted()) {
+ String urlString = EmbraceHttpPathOverride.getURLString(new EmbraceOkHttp3PathOverrideRequest(request), e.getCustomPath());
+
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ urlString,
+ HttpMethod.fromString(request.method()),
+ startTime,
+ System.currentTimeMillis(),
+ causeName(e, UNKNOWN_EXCEPTION),
+ causeMessage(e, UNKNOWN_MESSAGE),
+ request.header(embrace.getTraceIdHeader()),
+ sdkFacade.isNetworkSpanForwardingEnabled() ? request.header(TRACEPARENT_HEADER_NAME) : null,
+ null
+ )
+ );
+ }
+ throw e;
+ } catch (Exception e) {
+ // we are interested in errors.
+ if (embrace.isStarted()) {
+ String urlString = EmbraceHttpPathOverride.getURLString(new EmbraceOkHttp3PathOverrideRequest(request));
+ String errorType = e.getClass().getCanonicalName();
+ String errorMessage = e.getMessage();
+
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ urlString,
+ HttpMethod.fromString(request.method()),
+ startTime,
+ System.currentTimeMillis(),
+ errorType != null ? errorType : UNKNOWN_EXCEPTION,
+ errorMessage != null ? errorMessage : UNKNOWN_MESSAGE,
+ request.header(embrace.getTraceIdHeader()),
+ sdkFacade.isNetworkSpanForwardingEnabled() ? request.header(TRACEPARENT_HEADER_NAME) : null,
+ null
+ )
+ );
+ }
+ throw e;
+ }
+ }
+}
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3NetworkInterceptor.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3NetworkInterceptor.java
new file mode 100644
index 0000000000..c667b28419
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3NetworkInterceptor.java
@@ -0,0 +1,253 @@
+package io.embrace.android.embracesdk.okhttp3;
+
+import static io.embrace.android.embracesdk.config.behavior.NetworkSpanForwardingBehavior.TRACEPARENT_HEADER_NAME;
+import static io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.logDebug;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.embrace.android.embracesdk.Embrace;
+import io.embrace.android.embracesdk.InternalApi;
+import io.embrace.android.embracesdk.internal.ApkToolsConfig;
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest;
+import io.embrace.android.embracesdk.network.http.EmbraceHttpPathOverride;
+import io.embrace.android.embracesdk.network.http.HttpMethod;
+import io.embrace.android.embracesdk.network.http.NetworkCaptureData;
+import okhttp3.Headers;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.internal.http.HttpHeaders;
+import okhttp3.internal.http.RealResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.GzipSource;
+import okio.RealBufferedSource;
+
+/**
+ * Custom OkHttp3 Interceptor implementation that will log the results of the network call
+ * to Embrace.io.
+ *
+ * This interceptor will only intercept network request and responses from client app.
+ * OkHttp3 network interceptors are added almost at the end of stack, they are closer to "Wire"
+ * so they are able to see catch "real requests".
+ *
+ * Network Interceptors
+ * - Able to operate on intermediate responses like redirects and retries.
+ * - Not invoked for cached responses that short-circuit the network.
+ * - Observe the data just as it will be transmitted over the network.
+ * - Access to the Connection that carries the request.
+ */
+@InternalApi
+public final class EmbraceOkHttp3NetworkInterceptor implements Interceptor {
+ static final String ENCODING_GZIP = "gzip";
+ static final String CONTENT_LENGTH_HEADER_NAME = "Content-Length";
+ static final String CONTENT_ENCODING_HEADER_NAME = "Content-Encoding";
+ static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";
+ static final String CONTENT_TYPE_EVENT_STREAM = "text/event-stream";
+ private static final String[] networkCallDataParts = new String[]{
+ "Response Headers",
+ "Request Headers",
+ "Query Parameters",
+ "Request Body",
+ "Response Body"
+ };
+
+ final Embrace embrace;
+ private final SdkFacade sdkFacade;
+
+ public EmbraceOkHttp3NetworkInterceptor() {
+ this(Embrace.getInstance(), new SdkFacade());
+ }
+
+ EmbraceOkHttp3NetworkInterceptor(Embrace embrace, SdkFacade sdkFacade) {
+ this.embrace = embrace;
+ this.sdkFacade = sdkFacade;
+ }
+
+ @Override
+ public Response intercept(Chain chain) throws IOException {
+ final Request originalRequest = chain.request();
+
+ if (ApkToolsConfig.IS_NETWORK_CAPTURE_DISABLED || !embrace.isStarted()) {
+ return chain.proceed(originalRequest);
+ }
+
+ boolean networkSpanForwardingEnabled = sdkFacade.isNetworkSpanForwardingEnabled();
+
+ String traceparent = null;
+ if (networkSpanForwardingEnabled && originalRequest.header(TRACEPARENT_HEADER_NAME) == null) {
+ traceparent = embrace.generateW3cTraceparent();
+ }
+
+ final Request request = traceparent == null ?
+ originalRequest : originalRequest.newBuilder().header(TRACEPARENT_HEADER_NAME, traceparent).build();
+
+ Response networkResponse = chain.proceed(request);
+ Response.Builder responseBuilder = networkResponse.newBuilder().request(request);
+
+ Long contentLength = null;
+ // Try to get the content length from the header
+ if (networkResponse.header(CONTENT_LENGTH_HEADER_NAME) != null) {
+ try {
+ contentLength = Long.parseLong(networkResponse.header(CONTENT_LENGTH_HEADER_NAME));
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+
+ // If we get the body for a server-sent events stream, then we will wait forever
+ String contentType = networkResponse.header(CONTENT_TYPE_HEADER_NAME);
+
+ // Tolerant of a charset specified in header,
+ // e.g. Content-Type: text/event-stream;charset=UTF-8
+ boolean serverSentEvent = contentType != null &&
+ contentType.startsWith(CONTENT_TYPE_EVENT_STREAM);
+
+ if (!serverSentEvent && contentLength == null) {
+ try {
+ BufferedSource source = networkResponse.body().source();
+ source.request(Long.MAX_VALUE);
+ contentLength = source.buffer().size();
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+
+ if (contentLength == null) {
+ // Otherwise default to zero
+ contentLength = 0L;
+ }
+
+ boolean shouldCaptureNetworkData = embrace.shouldCaptureNetworkBody(request.url().toString(), request.method());
+
+ if (shouldCaptureNetworkData &&
+ ENCODING_GZIP.equalsIgnoreCase(networkResponse.header(CONTENT_ENCODING_HEADER_NAME)) &&
+ HttpHeaders.promisesBody(networkResponse)) {
+ ResponseBody body = networkResponse.body();
+ if (body != null) {
+ Headers strippedHeaders = networkResponse.headers().newBuilder()
+ .removeAll(CONTENT_ENCODING_HEADER_NAME)
+ .removeAll(CONTENT_LENGTH_HEADER_NAME)
+ .build();
+ RealResponseBody realResponseBody =
+ new RealResponseBody(
+ contentType,
+ -1L,
+ new RealBufferedSource(new GzipSource(body.source())
+ )
+ );
+ responseBuilder.headers(strippedHeaders);
+ responseBuilder.body(realResponseBody);
+ }
+ }
+
+ Response response = responseBuilder.build();
+
+ NetworkCaptureData networkCaptureData = null;
+ if (shouldCaptureNetworkData) {
+ networkCaptureData = getNetworkCaptureData(request, response);
+ }
+
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ EmbraceHttpPathOverride.getURLString(new EmbraceOkHttp3PathOverrideRequest(request)),
+ HttpMethod.fromString(request.method()),
+ response.sentRequestAtMillis(),
+ response.receivedResponseAtMillis(),
+ request.body() != null ? request.body().contentLength() : 0,
+ contentLength,
+ response.code(),
+ request.header(embrace.getTraceIdHeader()),
+ networkSpanForwardingEnabled ? request.header(TRACEPARENT_HEADER_NAME) : null,
+ networkCaptureData)
+ );
+
+ return response;
+ }
+
+ private NetworkCaptureData getNetworkCaptureData(Request request, Response response) {
+ Map requestHeaders = null;
+ String requestQueryParams = null;
+ Map responseHeaders = null;
+ byte[] requestBodyBytes = null;
+ byte[] responseBodyBytes = null;
+ String dataCaptureErrorMessage = null;
+ int partsAcquired = 0;
+
+ try {
+ responseHeaders = getProcessedHeaders(response.headers().toMultimap());
+ partsAcquired++;
+ requestHeaders = getProcessedHeaders(request.headers().toMultimap());
+ partsAcquired++;
+ requestQueryParams = request.url().query();
+ partsAcquired++;
+ requestBodyBytes = getRequestBody(request);
+ partsAcquired++;
+ if (HttpHeaders.promisesBody(response)) {
+ final ResponseBody responseBody = response.body();
+ if (responseBody != null) {
+ BufferedSource okResponseBodySource = responseBody.source();
+ okResponseBodySource.request(Integer.MAX_VALUE);
+ responseBodyBytes = okResponseBodySource.getBuffer().snapshot().toByteArray();
+ }
+ }
+ } catch (Exception e) {
+ final StringBuilder errors = new StringBuilder();
+ for (int i = partsAcquired; i < 5; i++) {
+ errors.append("'").append(networkCallDataParts[i]).append("'");
+ if (i != 4) {
+ errors.append(", ");
+ }
+ }
+
+ dataCaptureErrorMessage = "There were errors in capturing the following part(s) of the network call: %s" + errors;
+ logDebug("Failure during the building of NetworkCaptureData. " + dataCaptureErrorMessage, e);
+ }
+
+ return new NetworkCaptureData(
+ requestHeaders,
+ requestQueryParams,
+ requestBodyBytes,
+ responseHeaders,
+ responseBodyBytes,
+ dataCaptureErrorMessage
+ );
+ }
+
+ private HashMap getProcessedHeaders(Map> properties) {
+ HashMap headers = new HashMap<>();
+
+ for (Map.Entry> h :
+ properties.entrySet()) {
+ StringBuilder builder = new StringBuilder();
+ for (String value : h.getValue()) {
+ if (value != null) {
+ builder.append(value);
+ }
+ }
+ headers.put(h.getKey(), builder.toString());
+ }
+
+ return headers;
+ }
+
+ private byte[] getRequestBody(final Request request) {
+ try {
+ final Request requestCopy = request.newBuilder().build();
+ RequestBody requestBody = requestCopy.body();
+ if (requestBody != null) {
+ final Buffer buffer = new Buffer();
+ requestBody.writeTo(buffer);
+ return buffer.readByteArray();
+ }
+ } catch (final IOException e) {
+ logDebug("Failed to capture okhttp request body.", e);
+ }
+ return null;
+ }
+}
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3PathOverrideRequest.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3PathOverrideRequest.java
new file mode 100644
index 0000000000..4c2b9003fd
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3PathOverrideRequest.java
@@ -0,0 +1,28 @@
+package io.embrace.android.embracesdk.okhttp3;
+
+import io.embrace.android.embracesdk.HttpPathOverrideRequest;
+import okhttp3.Request;
+
+class EmbraceOkHttp3PathOverrideRequest implements HttpPathOverrideRequest {
+
+ private final Request request;
+
+ EmbraceOkHttp3PathOverrideRequest(Request request) {
+ this.request = request;
+ }
+
+ @Override
+ public String getHeaderByName(String name) {
+ return request.header(name);
+ }
+
+ @Override
+ public String getOverriddenURL(String pathOverride) {
+ return request.url().newBuilder().encodedPath(pathOverride).build().toString();
+ }
+
+ @Override
+ public String getURLString() {
+ return request.url().toString();
+ }
+}
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/SdkFacade.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/SdkFacade.java
new file mode 100644
index 0000000000..d55e00268d
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/SdkFacade.java
@@ -0,0 +1,13 @@
+package io.embrace.android.embracesdk.okhttp3;
+
+import io.embrace.android.embracesdk.Embrace;
+import io.embrace.android.embracesdk.utils.NetworkUtils;
+
+/**
+ * Facade to call internal SDK methods that can be mocked for tests
+ */
+class SdkFacade {
+ boolean isNetworkSpanForwardingEnabled() {
+ return NetworkUtils.isNetworkSpanForwardingEnabled(Embrace.getInstance().getConfigService());
+ }
+}
diff --git a/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/swazzle/callback/okhttp3/OkHttpClient.java b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/swazzle/callback/okhttp3/OkHttpClient.java
new file mode 100644
index 0000000000..80667d8774
--- /dev/null
+++ b/embrace-android-okhttp3/src/main/java/io/embrace/android/embracesdk/okhttp3/swazzle/callback/okhttp3/OkHttpClient.java
@@ -0,0 +1,106 @@
+package io.embrace.android.embracesdk.okhttp3.swazzle.callback.okhttp3;
+
+import java.util.List;
+
+import io.embrace.android.embracesdk.InternalApi;
+import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger;
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3ApplicationInterceptor;
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor;
+import okhttp3.Interceptor;
+
+/**
+ * Callback hooks for the okhttp3.OkHttpClient class.
+ */
+@InternalApi
+public final class OkHttpClient {
+
+ private OkHttpClient() {
+ }
+
+ @InternalApi
+ public static final class Builder {
+
+ private Builder() {
+ }
+
+ /**
+ * As there was a way to clear the injected interceptors during the OkHttpClient
+ * initialization using the builder, we are hooking the build method as well, instead of
+ * just the Builder constructor.
+ *
+ * Once the build method is called, OkHTTP mushes everything and returns the OkHttpClient
+ * instance, where the developer has no way to alter any of the interceptors during or
+ * after this point, without having to rebuild the client.
+ */
+ @SuppressWarnings("MethodNameCheck")
+ public static void _preBuild(okhttp3.OkHttpClient.Builder thiz) {
+ InternalStaticEmbraceLogger.logDebug("Embrace OkHTTP Wrapper; onPrebuild");
+ addEmbraceInterceptors(thiz);
+ }
+
+ @SuppressWarnings("MethodNameCheck")
+ public static void _constructorOnPostBody(okhttp3.OkHttpClient.Builder thiz) {
+ InternalStaticEmbraceLogger.logDebug("Embrace OkHTTP Wrapper; onPostBody");
+ addEmbraceInterceptors(thiz);
+ }
+
+ /**
+ * Adds embrace interceptors if they don't exist already to the OkHTTPClient provided.
+ *
+ * @param thiz the OkHttpClient builder in matter.
+ */
+ private static void addEmbraceInterceptors(okhttp3.OkHttpClient.Builder thiz) {
+ try {
+ InternalStaticEmbraceLogger.logDebug("Embrace OkHTTP Wrapper;"
+ + " Adding interceptors");
+ addInterceptor(thiz.interceptors(), new EmbraceOkHttp3ApplicationInterceptor());
+ addInterceptor(thiz.networkInterceptors(), new EmbraceOkHttp3NetworkInterceptor());
+ } catch (NoSuchMethodError exception) {
+ // The customer may be overwriting OkHttpClient with their own implementation, and some of the
+ // methods we use are missing.
+ InternalStaticEmbraceLogger.logError("Altered OkHttpClient implementation, could not add OkHttp interceptor. ",
+ exception);
+ } catch (Exception exception) {
+ InternalStaticEmbraceLogger.logError("Could not add OkHttp interceptor. ", exception);
+ }
+ }
+
+ /**
+ * Adds the interceptor to the interceptors list if it doesn't exist already.
+ *
+ * @param interceptors list of existing interceptors.
+ * @param interceptor interceptor to be added.
+ */
+ private static void addInterceptor(List interceptors,
+ Interceptor interceptor) {
+ if (interceptors != null && !containsInstance(interceptors, interceptor.getClass())) {
+ interceptors.add(0, interceptor);
+ } else {
+ InternalStaticEmbraceLogger.logDebug(
+ "Not adding interceptor [" + interceptor.getClass().getSimpleName() + "]"
+ );
+ }
+ }
+
+ /**
+ * Checks for the existence in the elements list of an instance of the same class as the
+ * one provided in the arguments.
+ *
+ * @param elementsList list of elements.
+ * @param clazz class of the instance that's being checked if exists.
+ * @return if an instance of the provided class exists in the list of elements.
+ */
+ private static boolean containsInstance(List elementsList,
+ Class extends T> clazz) {
+ for (T classInstance : elementsList) {
+ if (clazz.isInstance(classInstance)) {
+ InternalStaticEmbraceLogger.logDebug(
+ "[" + clazz.getSimpleName() + "] already present in list"
+ );
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3InterceptorsTest.kt b/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3InterceptorsTest.kt
new file mode 100644
index 0000000000..087efd9f5e
--- /dev/null
+++ b/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/EmbraceOkHttp3InterceptorsTest.kt
@@ -0,0 +1,535 @@
+package io.embrace.android.embracesdk.okhttp3
+
+import io.embrace.android.embracesdk.Embrace
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
+import io.embrace.android.embracesdk.network.http.NetworkCaptureData
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3ApplicationInterceptor.UNKNOWN_EXCEPTION
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3ApplicationInterceptor.UNKNOWN_MESSAGE
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor.CONTENT_ENCODING_HEADER_NAME
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor.CONTENT_LENGTH_HEADER_NAME
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor.CONTENT_TYPE_EVENT_STREAM
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor.CONTENT_TYPE_HEADER_NAME
+import io.embrace.android.embracesdk.okhttp3.EmbraceOkHttp3NetworkInterceptor.ENCODING_GZIP
+import io.mockk.CapturingSlot
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.slot
+import io.mockk.verify
+import okhttp3.Headers
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import okhttp3.ResponseBody.Companion.toResponseBody
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import okio.Buffer
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import java.io.ByteArrayOutputStream
+import java.net.SocketException
+import java.util.zip.GZIPOutputStream
+
+internal class EmbraceOkHttp3InterceptorsTest {
+
+ companion object {
+ private const val requestHeaderName = "requestHeader"
+ private const val requestHeaderValue = "requestHeaderVal"
+ private const val defaultQueryString = "param=yesPlease"
+ private const val defaultPath = "/test/default-path"
+ private const val customPath = "/test/custom-path"
+ private const val requestBodyString = "hey body"
+ private const val requestBodySize = 8
+ private const val responseHeaderName = "responseHeader"
+ private const val responseBody = "{\"bodyString\" = \"stringstringstringstringstringstringstringstringstringstringstringstring\"}"
+ private const val responseBodySize = 91
+ private const val responseBodyGzippedSize = 43
+ private const val responseHeaderValue = "responseHeaderVal"
+ private const val TRACEPARENT_HEADER = "traceparent"
+ private const val CUSTOM_TRACEPARENT = "00-b583a45b2c7c813e0ebc6aa0835b9d98-b5475c618bb98e67-01"
+ private const val GENERATED_TRACEPARENT = "00-3c72a77a7b51af6fb3778c06d4c165ce-4c1d710fffc88e35-01"
+ }
+
+ private lateinit var server: MockWebServer
+ private lateinit var applicationInterceptor: EmbraceOkHttp3ApplicationInterceptor
+ private lateinit var networkInterceptor: EmbraceOkHttp3NetworkInterceptor
+ private lateinit var preNetworkInterceptorTestInterceptor: Interceptor
+ private lateinit var postNetworkInterceptorTestInterceptor: Interceptor
+ private lateinit var okHttpClient: OkHttpClient
+ private lateinit var mockEmbrace: Embrace
+ private lateinit var mockSdkFacade: SdkFacade
+ private lateinit var getRequestBuilder: Request.Builder
+ private lateinit var postRequestBuilder: Request.Builder
+ private lateinit var capturedEmbraceNetworkRequest: CapturingSlot
+ private var preNetworkInterceptorBeforeRequestSupplier: (Request) -> Request = { request -> request }
+ private var preNetworkInterceptorAfterResponseSupplier: (Response) -> Response = { response -> response }
+ private var postNetworkInterceptorBeforeRequestSupplier: (Request) -> Request = { request -> request }
+ private var postNetworkInterceptorAfterResponseSupplier: (Response) -> Response = { response -> response }
+ private var isSDKStarted = true
+ private var isNetworkSpanForwardingEnabled = false
+
+ @Before
+ fun setup() {
+ server = MockWebServer()
+ mockEmbrace = mockk(relaxed = true)
+ mockSdkFacade = mockk(relaxed = true)
+ applicationInterceptor = EmbraceOkHttp3ApplicationInterceptor(mockEmbrace, mockSdkFacade)
+ preNetworkInterceptorTestInterceptor = TestInspectionInterceptor(
+ beforeRequestSent = { request -> preNetworkInterceptorBeforeRequestSupplier.invoke(request) },
+ afterResponseReceived = { response -> preNetworkInterceptorAfterResponseSupplier.invoke(response) }
+ )
+ networkInterceptor = EmbraceOkHttp3NetworkInterceptor(mockEmbrace, mockSdkFacade)
+ postNetworkInterceptorTestInterceptor = TestInspectionInterceptor(
+ beforeRequestSent = { request -> postNetworkInterceptorBeforeRequestSupplier.invoke(request) },
+ afterResponseReceived = { response -> postNetworkInterceptorAfterResponseSupplier.invoke(response) }
+ )
+ okHttpClient = OkHttpClient.Builder()
+ .addInterceptor(applicationInterceptor)
+ .addNetworkInterceptor(preNetworkInterceptorTestInterceptor)
+ .addNetworkInterceptor(networkInterceptor)
+ .addNetworkInterceptor(postNetworkInterceptorTestInterceptor)
+ .build()
+ getRequestBuilder = Request.Builder()
+ .url(server.url("$defaultPath?$defaultQueryString"))
+ .get()
+ .header(requestHeaderName, requestHeaderValue)
+ postRequestBuilder = Request.Builder()
+ .url(server.url("$defaultPath?$defaultQueryString"))
+ .post(requestBodyString.toRequestBody())
+ .header(requestHeaderName, requestHeaderValue)
+ capturedEmbraceNetworkRequest = slot()
+ every { mockEmbrace.isStarted } answers { isSDKStarted }
+ every { mockEmbrace.shouldCaptureNetworkBody(any(), "POST") } answers { true }
+ every { mockEmbrace.shouldCaptureNetworkBody(any(), "GET") } answers { false }
+ every { mockEmbrace.recordNetworkRequest(capture(capturedEmbraceNetworkRequest)) } answers { }
+ every { mockEmbrace.generateW3cTraceparent() } answers { GENERATED_TRACEPARENT }
+ every { mockSdkFacade.isNetworkSpanForwardingEnabled } answers { isNetworkSpanForwardingEnabled }
+ }
+
+ @After
+ fun teardown() {
+ server.shutdown()
+ }
+
+ @Test
+ fun `completed successful requests with uncompressed responses are recorded properly`() {
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runAndValidatePostRequest(responseBodySize)
+
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runAndValidateGetRequest(responseBodySize)
+ }
+
+ @Test
+ fun `completed successful requests with gzipped responses are recorded properly`() {
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runAndValidatePostRequest(responseBodyGzippedSize)
+
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runAndValidateGetRequest(responseBodyGzippedSize)
+ }
+
+ @Test
+ fun `completed unsuccessful requests are recorded properly`() {
+ server.enqueue(createBaseMockResponse(500).setGzipBody(responseBody))
+ runAndValidatePostRequest(expectedResponseBodySize = responseBodyGzippedSize, expectedHttpStatus = 500)
+ }
+
+ @Test
+ fun `completed requests with custom paths are recorded properly`() {
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ postRequestBuilder.header("x-emb-path", customPath)
+ runAndValidatePostRequest(expectedResponseBodySize = responseBodyGzippedSize, expectedPath = customPath)
+ }
+
+ @Test
+ fun `incomplete requests with custom paths are recorded properly`() {
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ postRequestBuilder.header("x-emb-path", customPath)
+ runAndValidatePostRequest(expectedResponseBodySize = responseBodyGzippedSize, expectedPath = customPath)
+ }
+
+ @Test
+ fun `completed requests are not recorded if the SDK has not started`() {
+ isSDKStarted = false
+ server.enqueue(createBaseMockResponse())
+ runGetRequest()
+ server.enqueue(createBaseMockResponse())
+ runPostRequest()
+ verify(exactly = 0) { mockEmbrace.recordNetworkRequest(any()) }
+ }
+
+ @Test
+ fun `incomplete requests are not recorded if the SDK has not started`() {
+ isSDKStarted = false
+ preNetworkInterceptorBeforeRequestSupplier = { throw SocketException() }
+ assertThrows(SocketException::class.java) { runGetRequest() }
+ postRequestBuilder.header("x-emb-path", customPath)
+ preNetworkInterceptorBeforeRequestSupplier = { throw EmbraceCustomPathException(customPath, SocketException()) }
+ assertThrows(EmbraceCustomPathException::class.java) { runPostRequest() }
+ verify(exactly = 0) { mockEmbrace.recordNetworkRequest(any()) }
+ }
+
+ @Test
+ fun `EmbraceOkHttp3NetworkInterceptor does nothing if SDK not started`() {
+ isSDKStarted = false
+ server.enqueue(createBaseMockResponse())
+ runGetRequest()
+ verify(exactly = 0) { mockSdkFacade.isNetworkSpanForwardingEnabled }
+ verify(exactly = 0) { mockEmbrace.shouldCaptureNetworkBody(any(), any()) }
+ }
+
+ @Test
+ fun `check content length header intact with not-gzipped response body if network capture not enabled`() {
+ preNetworkInterceptorAfterResponseSupplier = ::checkUncompressedBodySize
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runGetRequest()
+ assertNull(capturedEmbraceNetworkRequest.captured.networkCaptureData)
+ }
+
+ @Test
+ fun `check content length header intact with gzipped response body if network capture not enabled`() {
+ preNetworkInterceptorAfterResponseSupplier = ::checkCompressedBodySize
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runGetRequest()
+ assertNull(capturedEmbraceNetworkRequest.captured.networkCaptureData)
+ }
+
+ @Test
+ fun `check response body is not gzipped and no errors in capturing response body data when body is not gzipped`() {
+ preNetworkInterceptorAfterResponseSupplier = ::checkUncompressedBodySize
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runAndValidatePostRequest(responseBodySize)
+ }
+
+ @Test
+ fun `check response body is not gzipped and no errors in capturing response body data when response body is gzipped`() {
+ preNetworkInterceptorAfterResponseSupplier = fun(response: Response): Response {
+ val responseBuilder: Response.Builder = response.newBuilder().request(response.request)
+ assertNull(response.header(CONTENT_ENCODING_HEADER_NAME))
+ val bodySize = response.body?.bytes()?.size
+ assertEquals(responseBodySize, bodySize)
+ assertEquals(-1L, response.body?.contentLength())
+ assertNull(response.header(CONTENT_LENGTH_HEADER_NAME))
+ return responseBuilder.build()
+ }
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runAndValidatePostRequest(responseBodyGzippedSize)
+ }
+
+ @Test
+ fun `check error when getting response body in network capture`() {
+ val bodyThatKills: Buffer = mockk(relaxed = true)
+ every { bodyThatKills.size } answers { 10 }
+ server.enqueue(createBaseMockResponse().setBody(bodyThatKills))
+ runPostRequest()
+ val networkCaptureData = checkNotNull(capturedEmbraceNetworkRequest.captured.networkCaptureData)
+ with(networkCaptureData) {
+ validateDefaultNonBodyNetworkCaptureData(this)
+ assertTrue(checkNotNull(dataCaptureErrorMessage).contains("Response Body"))
+ }
+ }
+
+ @Test
+ fun `check EmbraceOkHttp3ApplicationInterceptor can handle compressed response without content-length parameter`() {
+ preNetworkInterceptorAfterResponseSupplier = ::removeContentLengthFromResponse
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runAndValidatePostRequest(responseBodyGzippedSize)
+ }
+
+ @Test
+ fun `check EmbraceOkHttp3ApplicationInterceptor can handle uncompressed response without content-length parameter`() {
+ preNetworkInterceptorAfterResponseSupplier = ::removeContentLengthFromResponse
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runAndValidatePostRequest(responseBodySize)
+ }
+
+ @Test
+ fun `check EmbraceOkHttp3NetworkInterceptor can handle compressed response without content-length parameter`() {
+ postNetworkInterceptorAfterResponseSupplier = ::removeContentLengthFromResponse
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setGzipBody(responseBody))
+ runAndValidatePostRequest(responseBodyGzippedSize)
+ }
+
+ @Test
+ fun `check EmbraceOkHttp3NetworkInterceptor can handle uncompressed response without content-length parameter`() {
+ postNetworkInterceptorAfterResponseSupplier = ::removeContentLengthFromResponse
+ preNetworkInterceptorAfterResponseSupplier = ::consumeBody
+ server.enqueue(createBaseMockResponse().setBody(responseBody))
+ runAndValidatePostRequest(responseBodySize)
+ }
+
+ @Test
+ fun `streaming requests recorded properly`() {
+ postNetworkInterceptorAfterResponseSupplier = ::removeContentLengthFromResponse
+ server.enqueue(createBaseMockResponse().addHeader(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_EVENT_STREAM).setBody(responseBody))
+ runAndValidatePostRequest(0)
+ }
+
+ @Test
+ fun `exceptions with canonical name and message cause incomplete network request to be recorded with those values`() {
+ preNetworkInterceptorBeforeRequestSupplier = { throw SocketException("bad bad socket") }
+ assertThrows(SocketException::class.java) { runPostRequest() }
+ with(capturedEmbraceNetworkRequest.captured) {
+ assertEquals(SocketException::class.java.canonicalName, errorType)
+ assertEquals("bad bad socket", errorMessage)
+ }
+ }
+
+ @Test
+ fun `anonymous exception with no message causes incomplete network request to be recorded with empty error type and message values`() {
+ preNetworkInterceptorBeforeRequestSupplier = { throw object : Exception() {} }
+ assertThrows(Exception::class.java) { runPostRequest() }
+ with(capturedEmbraceNetworkRequest.captured) {
+ assertEquals(UNKNOWN_EXCEPTION, errorType)
+ assertEquals(UNKNOWN_MESSAGE, errorMessage)
+ }
+ }
+
+ @Test
+ fun `EmbraceCustomPathException records incomplete network request with custom path and the correct error type and message`() {
+ postRequestBuilder.header("x-emb-path", customPath)
+ preNetworkInterceptorBeforeRequestSupplier = { throw EmbraceCustomPathException(customPath, IllegalStateException("Burned")) }
+ assertThrows(EmbraceCustomPathException::class.java) { runPostRequest() }
+ with(capturedEmbraceNetworkRequest.captured) {
+ assertEquals(IllegalStateException::class.java.canonicalName, errorType)
+ assertEquals("Burned", errorMessage)
+ assertTrue(url.endsWith("$customPath?$defaultQueryString"))
+ }
+ }
+
+ @Test
+ fun `EmbraceCustomPathException with anonymous cause records request with custom path and empty error type and message`() {
+ postRequestBuilder.header("x-emb-path", customPath)
+ preNetworkInterceptorBeforeRequestSupplier = { throw EmbraceCustomPathException(customPath, object : Exception() {}) }
+ assertThrows(EmbraceCustomPathException::class.java) { runPostRequest() }
+ with(capturedEmbraceNetworkRequest.captured) {
+ assertEquals(UNKNOWN_EXCEPTION, errorType)
+ assertEquals(UNKNOWN_MESSAGE, errorMessage)
+ assertTrue(url.endsWith("$customPath?$defaultQueryString"))
+ }
+ }
+
+ @Test
+ fun `check traceparent not injected or forwarded by default for a complete request `() {
+ server.enqueue(createBaseMockResponse())
+ runPostRequest()
+ assertEquals(200, capturedEmbraceNetworkRequest.captured.responseCode)
+ assertNull(capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check existing traceparent not forwarded by default for a complete request`() {
+ server.enqueue(createBaseMockResponse())
+ postRequestBuilder.header(TRACEPARENT_HEADER, CUSTOM_TRACEPARENT)
+ runPostRequest()
+ assertEquals(200, capturedEmbraceNetworkRequest.captured.responseCode)
+ assertNull(capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check traceparent injected and forwarded for a complete request if feature flag is on`() {
+ isNetworkSpanForwardingEnabled = true
+ server.enqueue(createBaseMockResponse())
+ runPostRequest()
+ assertEquals(200, capturedEmbraceNetworkRequest.captured.responseCode)
+ assertEquals(GENERATED_TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check existing traceparent is forwarded for a complete request`() {
+ isNetworkSpanForwardingEnabled = true
+ server.enqueue(createBaseMockResponse())
+ postRequestBuilder.header(TRACEPARENT_HEADER, CUSTOM_TRACEPARENT)
+ runPostRequest()
+ assertEquals(200, capturedEmbraceNetworkRequest.captured.responseCode)
+ assertEquals(CUSTOM_TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check traceparent not injected and forwarded for requests that don't complete because of EmbraceCustomPathException`() {
+ isNetworkSpanForwardingEnabled = true
+ postRequestBuilder.header("x-emb-path", customPath)
+ preNetworkInterceptorBeforeRequestSupplier = { throw EmbraceCustomPathException(customPath, IllegalStateException()) }
+ assertThrows(EmbraceCustomPathException::class.java) { runPostRequest() }
+ assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
+ assertNull(capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check traceparent not injected and forwarded for incomplete requests`() {
+ isNetworkSpanForwardingEnabled = true
+ preNetworkInterceptorBeforeRequestSupplier = { throw NullPointerException("hell nah") }
+ assertThrows(NullPointerException::class.java) { runPostRequest() }
+ assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
+ assertNull(capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check existing traceparent forwarded for requests that don't complete because of EmbraceCustomPathException`() {
+ isNetworkSpanForwardingEnabled = true
+ postRequestBuilder.header("x-emb-path", customPath).header(TRACEPARENT_HEADER, CUSTOM_TRACEPARENT)
+ preNetworkInterceptorBeforeRequestSupplier = { throw EmbraceCustomPathException(customPath, IllegalStateException()) }
+ assertThrows(EmbraceCustomPathException::class.java) { runPostRequest() }
+ assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
+ assertEquals(CUSTOM_TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ @Test
+ fun `check existing traceparent forwarded incomplete requests`() {
+ isNetworkSpanForwardingEnabled = true
+ postRequestBuilder.header(TRACEPARENT_HEADER, CUSTOM_TRACEPARENT)
+ preNetworkInterceptorBeforeRequestSupplier = { throw SocketException("hell nah") }
+ assertThrows(SocketException::class.java) { runPostRequest() }
+ assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
+ assertEquals(CUSTOM_TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
+ }
+
+ private fun createBaseMockResponse(httpStatus: Int = 200) =
+ MockResponse()
+ .setResponseCode(httpStatus)
+ .addHeader(responseHeaderName, responseHeaderValue)
+
+ private fun MockResponse.setGzipBody(stringBody: String): MockResponse =
+ setBody(
+ Buffer().write(
+ ByteArrayOutputStream().use { byteArrayStream ->
+ GZIPOutputStream(byteArrayStream).use { gzipStream ->
+ gzipStream.write(stringBody.toByteArray())
+ gzipStream.finish()
+ }
+ byteArrayStream.toByteArray()
+ }
+ )
+ ).addHeader(CONTENT_ENCODING_HEADER_NAME, ENCODING_GZIP)
+
+ private fun runAndValidatePostRequest(
+ expectedResponseBodySize: Int,
+ expectedPath: String = defaultPath,
+ expectedHttpStatus: Int = 200
+ ) {
+ runPostRequest()
+ validateWholeRequest(
+ path = expectedPath,
+ httpStatus = expectedHttpStatus,
+ responseBodySize = expectedResponseBodySize,
+ httpMethod = "POST",
+ requestSize = requestBodySize,
+ responseBody = responseBody
+ )
+ }
+
+ private fun runAndValidateGetRequest(
+ expectedResponseBodySize: Int
+ ) {
+ runGetRequest()
+ validateWholeRequest(
+ path = defaultPath,
+ httpStatus = 200,
+ httpMethod = "GET",
+ requestSize = 0,
+ responseBodySize = expectedResponseBodySize,
+ responseBody = null
+ )
+ }
+
+ private fun runPostRequest() = assertNotNull(okHttpClient.newCall(postRequestBuilder.build()).execute())
+
+ private fun runGetRequest() = assertNotNull(okHttpClient.newCall(getRequestBuilder.build()).execute())
+
+ private fun validateWholeRequest(
+ path: String,
+ httpMethod: String,
+ httpStatus: Int,
+ requestSize: Int,
+ responseBodySize: Int,
+ errorType: String? = null,
+ errorMessage: String? = null,
+ traceId: String? = null,
+ w3cTraceparent: String? = null,
+ responseBody: String?
+ ) {
+ with(capturedEmbraceNetworkRequest) {
+ assertTrue(captured.url.endsWith("$path?$defaultQueryString"))
+ assertEquals(httpMethod, captured.httpMethod)
+
+ // assert expected start/end times when we fix the issue of not using a custom clock instance.
+ assertTrue(captured.startTime > 0)
+ assertTrue(captured.endTime > 0)
+
+ assertEquals(httpStatus, captured.responseCode)
+ assertEquals(requestSize.toLong(), captured.bytesOut)
+ assertEquals(responseBodySize.toLong(), captured.bytesIn)
+ assertEquals(errorType, captured.errorType)
+ assertEquals(errorMessage, captured.errorMessage)
+ assertEquals(traceId, captured.traceId)
+ assertEquals(w3cTraceparent, captured.w3cTraceparent)
+ if (responseBody != null) {
+ validateNetworkCaptureData(responseBody)
+ }
+ }
+ }
+
+ private fun validateNetworkCaptureData(responseBody: String) {
+ with(checkNotNull(capturedEmbraceNetworkRequest.captured.networkCaptureData)) {
+ validateDefaultNonBodyNetworkCaptureData(this)
+ assertEquals(responseBody, capturedResponseBody?.toResponseBody()?.string())
+ assertNull(dataCaptureErrorMessage)
+ }
+ }
+
+ private fun validateDefaultNonBodyNetworkCaptureData(networkCaptureData: NetworkCaptureData?) {
+ with(checkNotNull(networkCaptureData)) {
+ assertEquals(requestHeaderValue, requestHeaders?.get(requestHeaderName.toLowerCase()))
+ assertEquals(responseHeaderValue, responseHeaders?.get(responseHeaderName.toLowerCase()))
+ assertEquals(defaultQueryString, requestQueryParams)
+ val buffer = Buffer()
+ capturedRequestBody?.toRequestBody()?.writeTo(buffer)
+ assertEquals(requestBodyString, buffer.readUtf8())
+ }
+ }
+
+ private fun checkUncompressedBodySize(response: Response) = checkBodySize(response, responseBodySize, false)
+
+ private fun checkCompressedBodySize(response: Response) = checkBodySize(response, responseBodyGzippedSize, true)
+
+ private fun checkBodySize(response: Response, expectedSize: Int, compressed: Boolean): Response {
+ val responseBuilder: Response.Builder = response.newBuilder().request(response.request)
+ if (compressed) {
+ assertEquals(ENCODING_GZIP, response.header(CONTENT_ENCODING_HEADER_NAME))
+ } else {
+ assertNull(response.header(CONTENT_ENCODING_HEADER_NAME))
+ }
+ val bodySize = response.body?.bytes()?.size
+ assertEquals(expectedSize, bodySize)
+ assertEquals(expectedSize.toLong(), response.body?.contentLength())
+ assertEquals(expectedSize.toString(), response.header(CONTENT_LENGTH_HEADER_NAME))
+ return responseBuilder.build()
+ }
+
+ private fun removeContentLengthFromResponse(response: Response): Response {
+ val responseBuilder: Response.Builder = response.newBuilder().request(response.request)
+ val newHeaders: Headers = response.headers.newBuilder()
+ .removeAll(CONTENT_LENGTH_HEADER_NAME)
+ .build()
+ responseBuilder.headers(newHeaders)
+ return responseBuilder.build()
+ }
+
+ private fun consumeBody(response: Response): Response {
+ checkNotNull(response.body).bytes()
+ return response
+ }
+}
diff --git a/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/TestInspectionInterceptor.kt b/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/TestInspectionInterceptor.kt
new file mode 100644
index 0000000000..3ecee35fe2
--- /dev/null
+++ b/embrace-android-okhttp3/src/test/kotlin/io/embrace/android/embracesdk/okhttp3/TestInspectionInterceptor.kt
@@ -0,0 +1,22 @@
+package io.embrace.android.embracesdk.okhttp3
+
+import okhttp3.Interceptor
+import okhttp3.Request
+import okhttp3.Response
+import org.jetbrains.annotations.TestOnly
+import java.io.IOException
+
+/**
+ * [Interceptor] used for testing that allows you to inspect and modify the request and responses that pass through this chain
+ */
+internal class TestInspectionInterceptor(
+ private val beforeRequestSent: (Request) -> Request,
+ private val afterResponseReceived: (Response) -> Response
+) : Interceptor {
+
+ @TestOnly
+ @Throws(IOException::class)
+ override fun intercept(chain: Interceptor.Chain): Response {
+ return afterResponseReceived.invoke(chain.proceed(beforeRequestSent.invoke(chain.request())))
+ }
+}
diff --git a/embrace-android-sdk/.gitignore b/embrace-android-sdk/.gitignore
new file mode 100644
index 0000000000..1dcf4532ab
--- /dev/null
+++ b/embrace-android-sdk/.gitignore
@@ -0,0 +1,3 @@
+/build
+.DS_Store
+.cxx/
\ No newline at end of file
diff --git a/embrace-android-sdk/CMakeLists.txt b/embrace-android-sdk/CMakeLists.txt
new file mode 100644
index 0000000000..d560f2ec64
--- /dev/null
+++ b/embrace-android-sdk/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Add main CMake files as subdirectory - this allows creation of a test target.
+
+cmake_minimum_required(VERSION 3.4.1)
+project(TEST)
+add_subdirectory(src/main/cpp)
diff --git a/embrace-android-sdk/api/embrace-android-sdk.api b/embrace-android-sdk/api/embrace-android-sdk.api
new file mode 100644
index 0000000000..d8f3eb4158
--- /dev/null
+++ b/embrace-android-sdk/api/embrace-android-sdk.api
@@ -0,0 +1,308 @@
+public abstract interface annotation class io/embrace/android/embracesdk/BetaApi : java/lang/annotation/Annotation {
+}
+
+public final class io/embrace/android/embracesdk/BuildConfig {
+ public static final field BUILD_TYPE Ljava/lang/String;
+ public static final field DEBUG Z
+ public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
+ public static final field VERSION_CODE Ljava/lang/String;
+ public static final field VERSION_NAME Ljava/lang/String;
+ public fun ()V
+}
+
+public final class io/embrace/android/embracesdk/Embrace : io/embrace/android/embracesdk/EmbraceAndroidApi {
+ public fun addBreadcrumb (Ljava/lang/String;)V
+ public fun addSessionProperty (Ljava/lang/String;Ljava/lang/String;Z)Z
+ public fun addUserPersona (Ljava/lang/String;)V
+ public fun clearAllUserPersonas ()V
+ public fun clearUserAsPayer ()V
+ public fun clearUserEmail ()V
+ public fun clearUserIdentifier ()V
+ public fun clearUserPersona (Ljava/lang/String;)V
+ public fun clearUsername ()V
+ public fun createSpan (Ljava/lang/String;)Lio/embrace/android/embracesdk/spans/EmbraceSpan;
+ public fun createSpan (Ljava/lang/String;Lio/embrace/android/embracesdk/spans/EmbraceSpan;)Lio/embrace/android/embracesdk/spans/EmbraceSpan;
+ public fun endAppStartup ()V
+ public fun endAppStartup (Ljava/util/Map;)V
+ public fun endMoment (Ljava/lang/String;)V
+ public fun endMoment (Ljava/lang/String;Ljava/lang/String;)V
+ public fun endMoment (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
+ public fun endMoment (Ljava/lang/String;Ljava/util/Map;)V
+ public fun endSession ()V
+ public fun endSession (Z)V
+ public fun endView (Ljava/lang/String;)Z
+ public fun generateW3cTraceparent ()Ljava/lang/String;
+ public fun getConfigService ()Lio/embrace/android/embracesdk/config/ConfigService;
+ public fun getCurrentSessionId ()Ljava/lang/String;
+ public fun getDeviceId ()Ljava/lang/String;
+ public fun getFlutterInternalInterface ()Lio/embrace/android/embracesdk/FlutterInternalInterface;
+ public static fun getInstance ()Lio/embrace/android/embracesdk/Embrace;
+ public fun getLastRunEndState ()Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+ public fun getReactNativeInternalInterface ()Lio/embrace/android/embracesdk/ReactNativeInternalInterface;
+ public fun getSessionProperties ()Ljava/util/Map;
+ public fun getTraceIdHeader ()Ljava/lang/String;
+ public fun getUnityInternalInterface ()Lio/embrace/android/embracesdk/UnityInternalInterface;
+ public fun isStarted ()Z
+ public fun isTracingAvailable ()Z
+ public fun logComposeTap (Landroid/util/Pair;Ljava/lang/String;)V
+ public fun logCustomStacktrace ([Ljava/lang/StackTraceElement;)V
+ public fun logCustomStacktrace ([Ljava/lang/StackTraceElement;Lio/embrace/android/embracesdk/Severity;)V
+ public fun logCustomStacktrace ([Ljava/lang/StackTraceElement;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;)V
+ public fun logCustomStacktrace ([Ljava/lang/StackTraceElement;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;Ljava/lang/String;)V
+ public fun logError (Ljava/lang/String;)V
+ public fun logException (Ljava/lang/Throwable;)V
+ public fun logException (Ljava/lang/Throwable;Lio/embrace/android/embracesdk/Severity;)V
+ public fun logException (Ljava/lang/Throwable;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;)V
+ public fun logException (Ljava/lang/Throwable;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;Ljava/lang/String;)V
+ public fun logHandledDartException (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ public fun logInfo (Ljava/lang/String;)V
+ public fun logInternalError (Ljava/lang/String;Ljava/lang/String;)V
+ public fun logInternalError (Ljava/lang/Throwable;)V
+ public fun logMessage (Ljava/lang/String;Lio/embrace/android/embracesdk/Severity;)V
+ public fun logMessage (Ljava/lang/String;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;)V
+ public fun logPushNotification (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
+ public fun logRnAction (Ljava/lang/String;JJLjava/util/Map;ILjava/lang/String;)V
+ public fun logRnView (Ljava/lang/String;)V
+ public fun logUnhandledDartException (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ public fun logWarning (Ljava/lang/String;)V
+ public fun recordCompletedSpan (Ljava/lang/String;JJ)Z
+ public fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/EmbraceSpan;)Z
+ public fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;)Z
+ public fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;Lio/embrace/android/embracesdk/spans/EmbraceSpan;)Z
+ public fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;Lio/embrace/android/embracesdk/spans/EmbraceSpan;Ljava/util/Map;Ljava/util/List;)Z
+ public fun recordCompletedSpan (Ljava/lang/String;JJLjava/util/Map;Ljava/util/List;)Z
+ public fun recordNetworkRequest (Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;)V
+ public fun recordSpan (Ljava/lang/String;Lio/embrace/android/embracesdk/spans/EmbraceSpan;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+ public fun recordSpan (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+ public fun removeSessionProperty (Ljava/lang/String;)Z
+ public fun sampleCurrentThreadDuringAnrs ()V
+ public fun setAppId (Ljava/lang/String;)Z
+ public fun setProcessStartedByNotification ()V
+ public fun setUserAsPayer ()V
+ public fun setUserEmail (Ljava/lang/String;)V
+ public fun setUserIdentifier (Ljava/lang/String;)V
+ public fun setUsername (Ljava/lang/String;)V
+ public fun shouldCaptureNetworkBody (Ljava/lang/String;Ljava/lang/String;)Z
+ public fun start (Landroid/content/Context;)V
+ public fun start (Landroid/content/Context;Z)V
+ public fun start (Landroid/content/Context;ZLio/embrace/android/embracesdk/Embrace$AppFramework;)V
+ public fun startMoment (Ljava/lang/String;)V
+ public fun startMoment (Ljava/lang/String;Ljava/lang/String;)V
+ public fun startMoment (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
+ public fun startView (Ljava/lang/String;)Z
+ public fun trackWebViewPerformance (Ljava/lang/String;Landroid/webkit/ConsoleMessage;)V
+ public fun trackWebViewPerformance (Ljava/lang/String;Ljava/lang/String;)V
+}
+
+public final class io/embrace/android/embracesdk/Embrace$AppFramework : java/lang/Enum {
+ public static final field FLUTTER Lio/embrace/android/embracesdk/Embrace$AppFramework;
+ public static final field NATIVE Lio/embrace/android/embracesdk/Embrace$AppFramework;
+ public static final field REACT_NATIVE Lio/embrace/android/embracesdk/Embrace$AppFramework;
+ public static final field UNITY Lio/embrace/android/embracesdk/Embrace$AppFramework;
+ public fun getValue ()I
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/Embrace$AppFramework;
+ public static fun values ()[Lio/embrace/android/embracesdk/Embrace$AppFramework;
+}
+
+public final class io/embrace/android/embracesdk/Embrace$LastRunEndState : java/lang/Enum {
+ public static final field CLEAN_EXIT Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+ public static final field CRASH Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+ public static final field INVALID Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+ public fun getValue ()I
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+ public static fun values ()[Lio/embrace/android/embracesdk/Embrace$LastRunEndState;
+}
+
+public final class io/embrace/android/embracesdk/EmbraceSamples {
+ public static final field INSTANCE Lio/embrace/android/embracesdk/EmbraceSamples;
+ public static final fun causeNdkIllegalInstruction ()V
+ public static final fun throwJvmException ()V
+ public static final fun triggerAnr ()V
+ public static final fun triggerLongAnr ()V
+ public static final fun verifyIntegration ()V
+}
+
+public abstract interface class io/embrace/android/embracesdk/HttpPathOverrideRequest {
+ public abstract fun getHeaderByName (Ljava/lang/String;)Ljava/lang/String;
+ public abstract fun getOverriddenURL (Ljava/lang/String;)Ljava/lang/String;
+ public abstract fun getURLString ()Ljava/lang/String;
+}
+
+public abstract interface annotation class io/embrace/android/embracesdk/InternalApi : java/lang/annotation/Annotation {
+}
+
+public final class io/embrace/android/embracesdk/LogExceptionType : java/lang/Enum {
+ public static final field HANDLED Lio/embrace/android/embracesdk/LogExceptionType;
+ public static final field NONE Lio/embrace/android/embracesdk/LogExceptionType;
+ public static final field UNHANDLED Lio/embrace/android/embracesdk/LogExceptionType;
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/LogExceptionType;
+ public static fun values ()[Lio/embrace/android/embracesdk/LogExceptionType;
+}
+
+public final class io/embrace/android/embracesdk/LogType : java/lang/Enum {
+ public static final field ERROR Lio/embrace/android/embracesdk/LogType;
+ public static final field INFO Lio/embrace/android/embracesdk/LogType;
+ public static final field WARNING Lio/embrace/android/embracesdk/LogType;
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/LogType;
+ public static fun values ()[Lio/embrace/android/embracesdk/LogType;
+}
+
+public final class io/embrace/android/embracesdk/Severity : java/lang/Enum {
+ public static final field ERROR Lio/embrace/android/embracesdk/Severity;
+ public static final field INFO Lio/embrace/android/embracesdk/Severity;
+ public static final field WARNING Lio/embrace/android/embracesdk/Severity;
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/Severity;
+ public static fun values ()[Lio/embrace/android/embracesdk/Severity;
+}
+
+public final class io/embrace/android/embracesdk/ViewSwazzledHooks {
+}
+
+public final class io/embrace/android/embracesdk/ViewSwazzledHooks$OnClickListener {
+ public static fun _preOnClick (Landroid/view/View$OnClickListener;Landroid/view/View;)V
+}
+
+public final class io/embrace/android/embracesdk/ViewSwazzledHooks$OnLongClickListener {
+ public static fun _preOnLongClick (Landroid/view/View$OnLongClickListener;Landroid/view/View;)V
+}
+
+public final class io/embrace/android/embracesdk/WebViewChromeClientSwazzledHooks {
+ public static fun _preOnConsoleMessage (Landroid/webkit/ConsoleMessage;)V
+}
+
+public final class io/embrace/android/embracesdk/WebViewClientSwazzledHooks {
+ public static fun _preOnPageStarted (Landroid/webkit/WebView;Ljava/lang/String;Landroid/graphics/Bitmap;)V
+}
+
+public abstract interface annotation class io/embrace/android/embracesdk/annotation/StartupActivity : java/lang/annotation/Annotation {
+}
+
+public final class io/embrace/android/embracesdk/network/EmbraceNetworkRequest {
+ public static fun fromCompletedRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJJJI)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromCompletedRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJJJILjava/lang/String;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromCompletedRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJJJILjava/lang/String;Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromCompletedRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJJJILjava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromIncompleteRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJLjava/lang/String;Ljava/lang/String;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromIncompleteRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromIncompleteRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public static fun fromIncompleteRequest (Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/HttpMethod;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;)Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;
+ public fun getBytesIn ()Ljava/lang/Long;
+ public fun getBytesOut ()Ljava/lang/Long;
+ public fun getBytesReceived ()Ljava/lang/Long;
+ public fun getBytesSent ()Ljava/lang/Long;
+ public fun getEndTime ()Ljava/lang/Long;
+ public fun getError ()Ljava/lang/Throwable;
+ public fun getErrorMessage ()Ljava/lang/String;
+ public fun getErrorType ()Ljava/lang/String;
+ public fun getHttpMethod ()Ljava/lang/String;
+ public fun getNetworkCaptureData ()Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;
+ public fun getResponseCode ()Ljava/lang/Integer;
+ public fun getStartTime ()Ljava/lang/Long;
+ public fun getTraceId ()Ljava/lang/String;
+ public fun getUrl ()Ljava/lang/String;
+ public fun getW3cTraceparent ()Ljava/lang/String;
+}
+
+public class io/embrace/android/embracesdk/network/http/EmbraceHttpPathOverride {
+ protected static final field PATH_OVERRIDE Ljava/lang/String;
+ public fun ()V
+ public static fun getURLString (Lio/embrace/android/embracesdk/HttpPathOverrideRequest;)Ljava/lang/String;
+ public static fun getURLString (Lio/embrace/android/embracesdk/HttpPathOverrideRequest;Ljava/lang/String;)Ljava/lang/String;
+}
+
+public final class io/embrace/android/embracesdk/network/http/HttpMethod : java/lang/Enum {
+ public static final field CONNECT Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field DELETE Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field GET Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field HEAD Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field OPTIONS Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field PATCH Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field POST Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field PUT Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static final field TRACE Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static fun fromInt (Ljava/lang/Integer;)Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static fun fromString (Ljava/lang/String;)Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/network/http/HttpMethod;
+ public static fun values ()[Lio/embrace/android/embracesdk/network/http/HttpMethod;
+}
+
+public final class io/embrace/android/embracesdk/network/http/NetworkCaptureData {
+ public fun (Ljava/util/Map;Ljava/lang/String;[BLjava/util/Map;[BLjava/lang/String;)V
+ public synthetic fun (Ljava/util/Map;Ljava/lang/String;[BLjava/util/Map;[BLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()[B
+ public final fun component4 ()Ljava/util/Map;
+ public final fun component5 ()[B
+ public final fun component6 ()Ljava/lang/String;
+ public final fun copy (Ljava/util/Map;Ljava/lang/String;[BLjava/util/Map;[BLjava/lang/String;)Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;
+ public static synthetic fun copy$default (Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;Ljava/util/Map;Ljava/lang/String;[BLjava/util/Map;[BLjava/lang/String;ILjava/lang/Object;)Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCapturedRequestBody ()[B
+ public final fun getCapturedResponseBody ()[B
+ public final fun getDataCaptureErrorMessage ()Ljava/lang/String;
+ public final fun getRequestBodySize ()I
+ public final fun getRequestHeaders ()Ljava/util/Map;
+ public final fun getRequestQueryParams ()Ljava/lang/String;
+ public final fun getResponseBodySize ()I
+ public final fun getResponseHeaders ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public abstract interface class io/embrace/android/embracesdk/spans/EmbraceSpan {
+ public abstract fun addAttribute (Ljava/lang/String;Ljava/lang/String;)Z
+ public abstract fun addEvent (Ljava/lang/String;)Z
+ public abstract fun addEvent (Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;)Z
+ public abstract fun getParent ()Lio/embrace/android/embracesdk/spans/EmbraceSpan;
+ public abstract fun getSpanId ()Ljava/lang/String;
+ public abstract fun getTraceId ()Ljava/lang/String;
+ public abstract fun isRecording ()Z
+ public abstract fun start ()Z
+ public abstract fun stop ()Z
+ public abstract fun stop (Lio/embrace/android/embracesdk/spans/ErrorCode;)Z
+}
+
+public final class io/embrace/android/embracesdk/spans/EmbraceSpanEvent {
+ public static final field Companion Lio/embrace/android/embracesdk/spans/EmbraceSpanEvent$Companion;
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()J
+ public final fun component3 ()Ljava/util/Map;
+ public final fun copy (Ljava/lang/String;JLjava/util/Map;)Lio/embrace/android/embracesdk/spans/EmbraceSpanEvent;
+ public static synthetic fun copy$default (Lio/embrace/android/embracesdk/spans/EmbraceSpanEvent;Ljava/lang/String;JLjava/util/Map;ILjava/lang/Object;)Lio/embrace/android/embracesdk/spans/EmbraceSpanEvent;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAttributes ()Ljava/util/Map;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getTimestampNanos ()J
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class io/embrace/android/embracesdk/spans/EmbraceSpanEvent$Companion {
+ public final fun create (Ljava/lang/String;JLjava/util/Map;)Lio/embrace/android/embracesdk/spans/EmbraceSpanEvent;
+}
+
+public final class io/embrace/android/embracesdk/spans/ErrorCode : java/lang/Enum, io/embrace/android/embracesdk/internal/spans/EmbraceAttributes$Attribute {
+ public static final field FAILURE Lio/embrace/android/embracesdk/spans/ErrorCode;
+ public static final field UNKNOWN Lio/embrace/android/embracesdk/spans/ErrorCode;
+ public static final field USER_ABANDON Lio/embrace/android/embracesdk/spans/ErrorCode;
+ public fun getCanonicalName ()Ljava/lang/String;
+ public fun keyName ()Ljava/lang/String;
+ public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/spans/ErrorCode;
+ public static fun values ()[Lio/embrace/android/embracesdk/spans/ErrorCode;
+}
+
+public abstract interface class io/embrace/android/embracesdk/spans/TracingApi {
+ public abstract fun createSpan (Ljava/lang/String;)Lio/embrace/android/embracesdk/spans/EmbraceSpan;
+ public abstract fun createSpan (Ljava/lang/String;Lio/embrace/android/embracesdk/spans/EmbraceSpan;)Lio/embrace/android/embracesdk/spans/EmbraceSpan;
+ public abstract fun isTracingAvailable ()Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJ)Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/EmbraceSpan;)Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;)Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;Lio/embrace/android/embracesdk/spans/EmbraceSpan;)Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJLio/embrace/android/embracesdk/spans/ErrorCode;Lio/embrace/android/embracesdk/spans/EmbraceSpan;Ljava/util/Map;Ljava/util/List;)Z
+ public abstract fun recordCompletedSpan (Ljava/lang/String;JJLjava/util/Map;Ljava/util/List;)Z
+ public abstract fun recordSpan (Ljava/lang/String;Lio/embrace/android/embracesdk/spans/EmbraceSpan;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+ public abstract fun recordSpan (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
+}
+
diff --git a/embrace-android-sdk/build.gradle b/embrace-android-sdk/build.gradle
new file mode 100644
index 0000000000..6a1e53c617
--- /dev/null
+++ b/embrace-android-sdk/build.gradle
@@ -0,0 +1,154 @@
+import io.embrace.gradle.Versions
+
+plugins {
+ id "internal-embrace-plugin"
+ id("org.jetbrains.kotlinx.kover") version "0.7.1"
+}
+
+description = "Embrace Android SDK: Core"
+
+apply plugin: "org.jetbrains.dokka"
+
+def NO_NDK = "noNdk"
+
+android {
+ useLibrary "android.test.runner"
+ useLibrary "android.test.base"
+ useLibrary "android.test.mock"
+ ndkVersion "${Versions.ndk}"
+
+ defaultConfig {
+ namespace = "io.embrace.android.embracesdk"
+ versionCode 53
+ versionName version
+ consumerProguardFiles "embrace-proguard.cfg"
+
+ // For library projects only, the BuildConfig.VERSION_NAME and BuildConfig.VERSION_CODE properties have been removed from the generated BuildConfig class
+ //
+ // https://developer.android.com/studio/releases/gradle-plugin#version_properties_removed_from_buildconfig_class_in_library_projects
+ buildConfigField "String", "VERSION_NAME", "\"${defaultConfig.versionName}\""
+ buildConfigField "String", "VERSION_CODE", "\"${defaultConfig.versionCode}\""
+ }
+
+ if (!project.hasProperty(NO_NDK)) {
+ externalNativeBuild {
+ cmake {
+ path file("CMakeLists.txt")
+ }
+ }
+ }
+ packagingOptions {
+ pickFirst "**/*.so"
+ }
+
+ sourceSets {
+ // Had to add a 'java' directory to store Java test files, as it doesn't get picked up as a test if I put it in
+ // the kotlin directory. If I've just screwed up somehow and this is actually possible, please consolidate.
+ test.java.srcDirs += "src/integrationTest/java"
+ test.kotlin.srcDirs += "src/integrationTest/kotlin"
+ }
+
+ buildFeatures {
+ buildConfig = true
+ }
+}
+
+// See: https://kotlin.github.io/kotlinx-kover/gradle-plugin/configuring#configuring-default-reports
+koverReport {
+ filters {
+ excludes {
+ // exclusion rules - classes to exclude from report
+ classes("io.embrace.android.embracesdk.BuildConfig")
+ }
+ }
+
+ androidReports("release") {
+ filters {
+ // override report filters for all reports for `release` build variant
+ // all filters specified by the level above cease to work
+ }
+
+ xml { /* XML report config for `release` build variant */ }
+ html {
+ title = "Embrace Android SDK merged coverage report"
+ }
+ verify {
+ // FUTURE: we can specify a minimum bound of coverage for new code here.
+ }
+ }
+}
+
+dependencies {
+ androidTestImplementation project(":test-server")
+ androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
+ androidTestImplementation project(path: ":embrace-android-sdk")
+
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.5.0"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
+ implementation "com.google.code.gson:gson:2.9.0"
+
+ implementation "io.opentelemetry:opentelemetry-api:${Versions.openTelemetry}"
+ implementation "io.opentelemetry:opentelemetry-sdk:${Versions.openTelemetry}"
+ implementation "io.opentelemetry:opentelemetry-context:${Versions.openTelemetry}"
+
+ // ProfileInstaller 1.2.0 requires compileSdk 32. 1.1.0 requires compileSdk 31.
+ // Please, don"t update it until we update compileSdk.
+ implementation "androidx.profileinstaller:profileinstaller:1.0.0"
+
+ testImplementation "io.mockk:mockk:1.12.2"
+ testImplementation "androidx.test:core:1.4.0"
+ testImplementation "androidx.test.ext:junit:1.1.3"
+ testImplementation "org.robolectric:robolectric:4.10.3"
+ testImplementation project(path: ":embrace-android-sdk")
+ testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
+
+ dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.7.10")
+}
+
+dokkaHtml {
+ outputDirectory.set(rootProject.file("docs"))
+ dokkaSourceSets {
+ configureEach {
+ perPackageOption {
+ skipDeprecated.set(false)
+ reportUndocumented.set(true) // Emit warnings about not documented members
+ includeNonPublic.set(false)
+
+ // Match all packages and suppress them
+ perPackageOption {
+ matchingRegex.set(".*.networking.*?")
+ suppress.set(true)
+ }
+
+ perPackageOption {
+ matchingRegex.set(".*.network.*?")
+ suppress.set(true)
+ }
+
+ perPackageOption {
+ matchingRegex.set(".*.utils.*?")
+ suppress.set(true)
+ }
+
+ perPackageOption {
+ matchingRegex.set(".*.internal.*?")
+ suppress.set(true)
+ }
+
+ // unsuppress only the ones that we care about
+ perPackageOption {
+ matchingRegex.set("embrace-android-sdk")
+ suppress.set(false)
+ }
+ }
+ }
+ named("main") {
+ noAndroidSdkLink.set(false)
+ }
+ }
+}
+
+task publishLocal { dependsOn("publishMavenPublicationToMavenLocal") }
+task publishSonatype { dependsOn("publishMavenPublicationToMavenSonatype") }
+task publishQa { dependsOn("publishMavenPublicationToQaRepository") }
diff --git a/embrace-android-sdk/config/detekt/baseline.xml b/embrace-android-sdk/config/detekt/baseline.xml
new file mode 100644
index 0000000000..5703cad343
--- /dev/null
+++ b/embrace-android-sdk/config/detekt/baseline.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/embrace-android-sdk/embrace-proguard.cfg b/embrace-android-sdk/embrace-proguard.cfg
new file mode 100644
index 0000000000..627bb5610b
--- /dev/null
+++ b/embrace-android-sdk/embrace-proguard.cfg
@@ -0,0 +1,24 @@
+-keepattributes Exceptions, InnerClasses, Signature, LineNumberTable, SourceFile
+
+## Proguard configuration for Embrace
+-keep class io.embrace.android.embracesdk.** { *; }
+-dontwarn io.embrace.android.embracesdk.**
+
+## Proguard configuration for OkHTTP3 / Okio
+-dontwarn okhttp3.**
+-dontwarn okio.**
+
+## Proguard configuration for Gson
+-keepattributes Signature
+-keepattributes *Annotation*
+-dontwarn sun.misc.**
+-keep class com.google.gson.examples.android.model.** { *; }
+-keep class * implements com.google.gson.TypeAdapterFactory
+-keep class * implements com.google.gson.JsonSerializer
+-keep class * implements com.google.gson.JsonDeserializer
+-keep class com.google.gson.reflect.TypeToken { *; }
+-keep class * extends com.google.gson.reflect.TypeToken
+
+## Proguard configuration for Arrow
+-keep class java9.** { *; }
+-dontwarn java9.**
\ No newline at end of file
diff --git a/embrace-android-sdk/gradle/wrapper/gradle-wrapper.jar b/embrace-android-sdk/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..7a3265ee94
Binary files /dev/null and b/embrace-android-sdk/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/embrace-android-sdk/gradle/wrapper/gradle-wrapper.properties b/embrace-android-sdk/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..3442499c52
--- /dev/null
+++ b/embrace-android-sdk/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Sep 24 14:41:58 PDT 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/embrace-android-sdk/gradlew b/embrace-android-sdk/gradlew
new file mode 100644
index 0000000000..cccdd3d517
--- /dev/null
+++ b/embrace-android-sdk/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/embrace-android-sdk/gradlew.bat b/embrace-android-sdk/gradlew.bat
new file mode 100644
index 0000000000..f9553162f1
--- /dev/null
+++ b/embrace-android-sdk/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/embrace-android-sdk/lint-baseline.xml b/embrace-android-sdk/lint-baseline.xml
new file mode 100644
index 0000000000..0458e5d1b7
--- /dev/null
+++ b/embrace-android-sdk/lint-baseline.xml
@@ -0,0 +1,1830 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embrace-android-sdk/proguard-rules.pro b/embrace-android-sdk/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/embrace-android-sdk/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/embrace-android-sdk/src/androidTest/AndroidManifest.xml b/embrace-android-sdk/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000..86a482773c
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/expected-webview-core-vital.json b/embrace-android-sdk/src/androidTest/assets/golden-files/expected-webview-core-vital.json
new file mode 100644
index 0000000000..d2e3900564
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/expected-webview-core-vital.json
@@ -0,0 +1,22 @@
+{
+ "ts": 1111,
+ "u": "https://embrace.io/",
+ "vt": [
+ {
+ "key": "EMBRACE_METRIC",
+ "n": "layout-shift",
+ "t": "CLS",
+ "st": 1111,
+ "d": 10,
+ "s": 0.1
+ },
+ {
+ "key": "EMBRACE_METRIC",
+ "n": "layout-shift",
+ "t": "LCP",
+ "st": 2222,
+ "d": 10,
+ "s": 0
+ }
+ ]
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-event.json
new file mode 100644
index 0000000000..d7b75e029b
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-event.json
@@ -0,0 +1,36 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log error",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "error"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-and-message-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-and-message-event.json
new file mode 100644
index 0000000000..4db0d9edb7
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-and-message-event.json
@@ -0,0 +1,40 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "pr": {
+ },
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "em": "Exception message",
+ "en": "NullPointerException",
+ "f": 1,
+ "et": "handled",
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "log message",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "error"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-event.json
new file mode 100644
index 0000000000..3c60b278f6
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-exception-event.json
@@ -0,0 +1,38 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "handled",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Another log error",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "error",
+ "en": "Exception",
+ "em": "Another log error"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-property-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-property-event.json
new file mode 100644
index 0000000000..214cd51887
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-error-with-property-event.json
@@ -0,0 +1,39 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "pr": {
+ "error": "test property"
+ },
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log error",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "error"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-handled-exception.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-handled-exception.json
new file mode 100644
index 0000000000..31f842811a
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-handled-exception.json
@@ -0,0 +1,41 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "pr": {
+ "error": "test property"
+ },
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "em": "Exception message",
+ "en": "NullPointerException",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Exception message",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "error"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-event.json
new file mode 100644
index 0000000000..36f1a5afe5
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-event.json
@@ -0,0 +1,36 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log info",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "info"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-fail-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-fail-event.json
new file mode 100644
index 0000000000..802821b373
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-fail-event.json
@@ -0,0 +1,36 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log info fail",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "info"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-with-property-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-with-property-event.json
new file mode 100644
index 0000000000..5033b9d2c3
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-info-with-property-event.json
@@ -0,0 +1,39 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "pr": {
+ "info": "test property"
+ },
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log info with property",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "info"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/log-warning-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/log-warning-event.json
new file mode 100644
index 0000000000..6009f3d8e6
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/log-warning-event.json
@@ -0,0 +1,36 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "et": "none",
+ "f": 1,
+ "li": "__EMBRACE_TEST_IGNORE__",
+ "n": "Test log warning",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "warning"
+ },
+ "sk": {
+ "tt": "__EMBRACE_TEST_IGNORE__"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-end-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-end-event.json
new file mode 100644
index 0000000000..dcfd105be8
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-end-event.json
@@ -0,0 +1,16 @@
+{
+ "et": {
+ "st": "active",
+ "du": "__EMBRACE_TEST_IGNORE__",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "n": "my_moment",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "end"
+ },
+ "p": "__EMBRACE_TEST_IGNORE__",
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-start-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-start-event.json
new file mode 100644
index 0000000000..bb53e9f3d4
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-start-event.json
@@ -0,0 +1,31 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "th": 5000,
+ "n": "my_moment",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "start"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-end-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-end-event.json
new file mode 100644
index 0000000000..962c30075d
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-end-event.json
@@ -0,0 +1,16 @@
+{
+ "et": {
+ "st": "active",
+ "du": "__EMBRACE_TEST_IGNORE__",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "n": "my_moment",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "late"
+ },
+ "p": "__EMBRACE_TEST_IGNORE__",
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-start-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-start-event.json
new file mode 100644
index 0000000000..a942ec9d05
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-custom-with-properties-start-event.json
@@ -0,0 +1,35 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "pr": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "th": 5000,
+ "n": "my_moment",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "start"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-end-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-end-event.json
new file mode 100644
index 0000000000..d697046940
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-end-event.json
@@ -0,0 +1,16 @@
+{
+ "et": {
+ "st": "active",
+ "du": "__EMBRACE_TEST_IGNORE__",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "n": "_startup",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "end"
+ },
+ "p": "__EMBRACE_TEST_IGNORE__",
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-late-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-late-event.json
new file mode 100644
index 0000000000..265ea6c88a
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-late-event.json
@@ -0,0 +1,16 @@
+{
+ "et": {
+ "st": "active",
+ "du": "__EMBRACE_TEST_IGNORE__",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "n": "_startup",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "late"
+ },
+ "p": "__EMBRACE_TEST_IGNORE__",
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-start-event.json b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-start-event.json
new file mode 100644
index 0000000000..cd84bacd53
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/moment-startup-start-event.json
@@ -0,0 +1,31 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "et": {
+ "st": "active",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "th": 5000,
+ "n": "_startup",
+ "sc": false,
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "sp": {},
+ "ts": "__EMBRACE_TEST_IGNORE__",
+ "t": "start"
+ },
+ "u": "__EMBRACE_TEST_IGNORE__",
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/session-end.json b/embrace-android-sdk/src/androidTest/assets/golden-files/session-end.json
new file mode 100644
index 0000000000..64d4b8e39b
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/session-end.json
@@ -0,0 +1,109 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "br": {
+ "cb": "__EMBRACE_TEST_IGNORE__",
+ "cv": [],
+ "rna": [],
+ "pn": [],
+ "tb": [],
+ "vb": [
+ {
+ "vn": "io.embrace.android.embracesdk.internal.MockActivity",
+ "st": "__EMBRACE_TEST_IGNORE__"
+ }
+ ],
+ "wv": []
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "p": {
+ "nr": {
+ "v2": {
+ "c": {},
+ "r": []
+ }
+ },
+ "anr": [],
+ "anr_pe": [],
+ "ds": {
+ "as": "__EMBRACE_TEST_IGNORE__",
+ "fs": "__EMBRACE_TEST_IGNORE__"
+ },
+ "aei": "__EMBRACE_TEST_IGNORE__",
+ "ga": [],
+ "mw": [],
+ "ns": "__EMBRACE_TEST_IGNORE__",
+ "lp": "__EMBRACE_TEST_IGNORE__"
+ },
+ "s": {
+ "as": "__EMBRACE_TEST_IGNORE__",
+ "ty": "__EMBRACE_TEST_IGNORE__",
+ "bf": "__EMBRACE_TEST_IGNORE__",
+ "cs": true,
+ "et": "__EMBRACE_TEST_IGNORE__",
+ "em": "s",
+ "ce": true,
+ "el": [],
+ "lec": 0,
+ "ss": "__EMBRACE_TEST_IGNORE__",
+ "il": [],
+ "lic": 0,
+ "ht": "__EMBRACE_TEST_IGNORE__",
+ "sn": "__EMBRACE_TEST_IGNORE__",
+
+ "sp": {},
+ "si": "__EMBRACE_TEST_IGNORE__",
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "st": "__EMBRACE_TEST_IGNORE__",
+ "sm": "s",
+ "sd": "__EMBRACE_TEST_IGNORE__",
+ "sdt": "__EMBRACE_TEST_IGNORE__",
+ "ue": 0,
+ "lwc": 0,
+ "nc": [],
+ "wl": [],
+ "wvi_beta": [
+ {
+ "ts": 1111,
+ "t": "myWebView",
+ "u": "https://embrace.io/",
+ "vt": [
+ {
+ "d": 10,
+ "n": "layout-shift",
+ "s": 0.0,
+ "st": 2222,
+ "t": "LCP"
+ },
+ {
+ "d": 10,
+ "n": "layout-shift",
+ "s": 0.1,
+ "st": 1111,
+ "t": "CLS"
+ }
+ ]
+ }
+ ]
+ },
+ "u": {
+ "id": "some id",
+ "em": "user@email.com",
+ "un": "John Doe",
+ "per": ["first_day"]
+ },
+ "v": 13
+}
diff --git a/embrace-android-sdk/src/androidTest/assets/golden-files/session-start.json b/embrace-android-sdk/src/androidTest/assets/golden-files/session-start.json
new file mode 100644
index 0000000000..403e600e1d
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/assets/golden-files/session-start.json
@@ -0,0 +1,30 @@
+{
+ "a": {
+ "f": 1,
+ "vu": false,
+ "vul": false,
+ "v": "1.1.2",
+ "fl": "default test build flavor",
+ "bi": "default test build id",
+ "bt": "default test build type",
+ "bv": "5",
+ "e": "dev",
+ "ou": false,
+ "oul": false,
+ "sdc": "53",
+ "sdk": "__EMBRACE_TEST_IGNORE__"
+ },
+ "d": "__EMBRACE_TEST_IGNORE__",
+ "s": {
+ "as": "__EMBRACE_TEST_IGNORE__",
+ "ty":"__EMBRACE_TEST_IGNORE__",
+ "cs": true,
+ "sn": "__EMBRACE_TEST_IGNORE__",
+
+ "sp": {},
+ "id": "__EMBRACE_TEST_IGNORE__",
+ "st": "__EMBRACE_TEST_IGNORE__",
+ "sm": "s"
+ },
+ "v": 13
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/AnrIntegrationTest.kt b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/AnrIntegrationTest.kt
new file mode 100644
index 0000000000..ab3420d4d4
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/AnrIntegrationTest.kt
@@ -0,0 +1,315 @@
+package io.embrace.android.embracesdk
+
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import io.embrace.android.embracesdk.internal.EmbraceSerializer
+import io.embrace.android.embracesdk.payload.AnrInterval
+import io.embrace.android.embracesdk.payload.AnrSample
+import io.embrace.android.embracesdk.payload.SessionMessage
+import io.embrace.android.embracesdk.payload.ThreadInfo
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.zip.GZIPInputStream
+import okhttp3.mockwebserver.RecordedRequest
+
+// number of intervals we create in the test
+private const val EXPECTED_INTERVALS = 6
+
+// we allow for extra or missing samples to account for natural differences in thread scheduling
+private const val SAMPLE_TOLERANCE = 12
+
+// allow some tolerance for how long an ANR interval lasts
+private const val INTERVAL_DURATION_TOLERANCE = 500
+
+// allow the SDK to initialize first
+private const val SDK_INIT_TOLERANCE_MS = 1000L
+
+// gap between intervals we trigger in the test case
+private const val INTERVAL_GAP_MS = 1000L
+
+// how long we wait for the test to complete before aborting
+private const val TEST_TIMEOUT_SECS = 60L
+
+// default config for sampling interval in ms
+private const val SAMPLE_INTERVAL_MS = 100
+
+// default threshold for creating an ANR interval
+private const val ANR_THRESHOLD_MS = SAMPLE_INTERVAL_MS * 10
+
+// maximum number of samples capture
+private const val MAX_SAMPLES = 80
+
+private data class ExpectedIntervalData(
+ val intervalCode: Int,
+ val sampleCode: Int,
+ val expectedSamples: Int,
+ val expectedMethods: List
+) {
+ val expectedDuration = (expectedSamples * SAMPLE_INTERVAL_MS) + ANR_THRESHOLD_MS
+}
+
+private val firstInterval = ExpectedIntervalData(
+ AnrInterval.CODE_DEFAULT,
+ AnrSample.CODE_DEFAULT,
+ 100,
+ listOf(
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepThreeSeconds",
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepTwoSeconds",
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepOneSecond",
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepFiveSeconds"
+ )
+)
+
+private val secondInterval = ExpectedIntervalData(
+ AnrInterval.CODE_SAMPLES_CLEARED,
+ AnrSample.CODE_DEFAULT,
+ 10,
+ listOf(
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepTwoSeconds"
+ )
+)
+
+private val thirdInterval = ExpectedIntervalData(
+ AnrInterval.CODE_DEFAULT,
+ AnrSample.CODE_DEFAULT,
+ 20,
+ listOf(
+ "io.embrace.android.embracesdk.AnrIntegrationTest.sleepThreeSeconds"
+ )
+)
+
+private val fourthInterval = thirdInterval.copy()
+private val fifthInterval = thirdInterval.copy()
+private val sixthInterval = thirdInterval.copy()
+
+internal class AnrIntegrationTest : BaseTest() {
+
+ private val handler = Handler(Looper.getMainLooper())
+ private lateinit var latch: CountDownLatch
+ private val serializer = EmbraceSerializer()
+
+ @Before
+ fun setup() {
+ latch = CountDownLatch(EXPECTED_INTERVALS)
+ startEmbraceInForeground()
+ Embrace.getImpl().endAppStartup(null)
+ }
+
+ private fun readBodyAsSessionMessage(request: RecordedRequest): SessionMessage {
+ val stream = request.body.inputStream()
+ GZIPInputStream(stream).bufferedReader().use {
+ return gson.fromJson(it, SessionMessage::class.java)
+ }
+ }
+
+ /**
+ * Verifies that a session end message is sent and contains ANR information. The
+ * test triggers 6 ANR intervals by blocking the main thread, with a gap in between
+ * intervals. This covers a variety of scenarios:
+ *
+ * - Exceeding max ANR samples for one interval
+ * - Exceeding max ANR intervals for session
+ * - Validates that timestamps are correct & that samples broadly contain the expected
+ * information. I've used a 'tolerance' for most of these assertions because the number
+ * of samples/thread traces will vary on each test run
+ * - Added functions to deserialize received session payloads to allow asserting against the received JSON
+ * - Bumped max wait for pauseLatch as I noticed a few timeouts when running locally
+ */
+ @Test
+ fun testAnrIntervalsInSessionEndMessage() {
+ startAnrIntervals()
+
+ // wait a reasonable time period before assuming the test is deadlocked
+ latch.await(TEST_TIMEOUT_SECS, TimeUnit.SECONDS)
+
+ // trigger a session
+ sendBackground()
+
+ // ignore startup moment end request that is validated in other tests
+ waitForRequest()
+
+ // validate ANRs with JUnit assertions rather than golden file
+ waitForRequest { request ->
+ val payload = readBodyAsSessionMessage(request)
+ assertNotNull(payload)
+ val perfInfo by lazy { serializer.toJson(payload.performanceInfo) }
+ val intervals = checkNotNull(payload.performanceInfo?.anrIntervals) {
+ "No ANR intervals in payload. p=$perfInfo"
+ }
+ assertEquals(
+ "Unexpected number of intervals. $perfInfo",
+ EXPECTED_INTERVALS,
+ intervals.size
+ )
+
+ validateInterval(0, intervals, firstInterval)
+ validateInterval(1, intervals, secondInterval)
+ validateInterval(2, intervals, thirdInterval)
+ validateInterval(3, intervals, fourthInterval)
+ validateInterval(4, intervals, fifthInterval)
+ validateInterval(5, intervals, sixthInterval)
+ }
+ }
+
+ private fun validateInterval(
+ index: Int,
+ intervals: List,
+ data: ExpectedIntervalData
+ ) {
+ val interval = intervals[index]
+ val errMsg: String by lazy {
+ "Assertion failed for interval $index. ${serializer.toJson(intervals)}"
+ }
+
+ // validate interval code
+ assertEquals(errMsg, data.intervalCode, interval.code)
+
+ // validate interval type
+ assertEquals(errMsg, AnrInterval.Type.UI, interval.type)
+
+ // validate interval lastKnownTime is null
+ assertNull(errMsg, interval.lastKnownTime)
+
+ // validate the duration (calculated via startTime/endTime) is around what we'd expect
+ val duration = interval.duration()
+ assertWithTolerance(
+ errMsg,
+ data.expectedDuration,
+ duration.toInt(),
+ INTERVAL_DURATION_TOLERANCE
+ )
+
+ if (interval.code != AnrInterval.CODE_SAMPLES_CLEARED) {
+ validateSamples(interval, index, errMsg, data)
+ }
+ }
+
+ private fun validateSamples(
+ interval: AnrInterval,
+ index: Int,
+ errMsg: String,
+ data: ExpectedIntervalData
+ ) {
+ // validate there was roughly 1 sample every 100ms
+ val samples = checkNotNull(interval.anrSampleList?.samples) {
+ "Interval $index was missing samples completely. $errMsg\ninterval=${
+ serializer.toJson(
+ interval
+ )
+ }"
+ }
+ assertWithTolerance(errMsg, data.expectedSamples, samples.size, SAMPLE_TOLERANCE)
+
+ // validate the samples all recorded their overhead
+ assertTrue(errMsg, samples.all { checkNotNull(it.sampleOverheadMs) >= 0 })
+
+ // validate that all timestamps are ascending
+ assertTrue(errMsg, samples == samples.sortedBy(AnrSample::timestamp))
+
+ // validate the samples have the expected code
+ if (data.expectedSamples <= MAX_SAMPLES) {
+ assertTrue(errMsg, samples.all { it.code == data.sampleCode })
+ } else {
+ val withStacktraces = samples.count { it.code == data.sampleCode }
+ val withoutStacktraces =
+ samples.count { it.code == AnrSample.CODE_SAMPLE_LIMIT_REACHED }
+
+ assertWithTolerance(errMsg, MAX_SAMPLES, withStacktraces, SAMPLE_TOLERANCE)
+ assertWithTolerance(
+ errMsg,
+ data.expectedSamples - MAX_SAMPLES,
+ withoutStacktraces,
+ SAMPLE_TOLERANCE
+ )
+ assertTrue(
+ errMsg,
+ samples.filter { it.code == AnrSample.CODE_SAMPLE_LIMIT_REACHED }
+ .all { it.threads == null })
+ }
+
+ // validate that threads contains the method names in the expected order
+ val threads: List> = samples.mapNotNull(AnrSample::threads)
+ val nonEmptyThreads: List> = threads
+ .filter(List::isNotEmpty)
+ .flatten()
+ .map { checkNotNull(it.lines) }
+ assertTrue(errMsg, nonEmptyThreads.size >= data.expectedMethods.size)
+
+ data.expectedMethods.forEachIndexed { k, method ->
+ assertEquals(
+ errMsg,
+ 1,
+ nonEmptyThreads[k].count { it.startsWith(method) })
+ }
+ }
+
+ private fun startAnrIntervals() {
+ handler.postDelayed(Runnable {
+ Log.i("Embrace", "Starting first ANR interval")
+ sleepThreeSeconds()
+ sleepTwoSeconds()
+ sleepOneSecond()
+ sleepFiveSeconds()
+ latch.countDown()
+ scheduleNextMainThreadWork { produceSecondAnrInterval() }
+ }, SDK_INIT_TOLERANCE_MS)
+ }
+
+ private fun produceSecondAnrInterval() {
+ Log.i("Embrace", "Starting second ANR interval")
+ sleepTwoSeconds()
+ latch.countDown()
+ scheduleNextMainThreadWork { produceThirdAnrInterval() }
+ }
+
+ private fun produceThirdAnrInterval() {
+ Log.i("Embrace", "Starting third ANR interval")
+ sleepThreeSeconds()
+ latch.countDown()
+ scheduleNextMainThreadWork { produceFourthAnrInterval() }
+ }
+
+ private fun produceFourthAnrInterval() {
+ Log.i("Embrace", "Starting fourth ANR interval")
+ sleepThreeSeconds()
+ latch.countDown()
+ scheduleNextMainThreadWork { produceFifthAnrInterval() }
+ }
+
+ private fun produceFifthAnrInterval() {
+ Log.i("Embrace", "Starting fifth ANR interval")
+ sleepThreeSeconds()
+ latch.countDown()
+ scheduleNextMainThreadWork { produceSixthAnrInterval() }
+ }
+
+ private fun produceSixthAnrInterval() {
+ Log.i("Embrace", "Starting sixth ANR interval")
+ sleepThreeSeconds()
+ latch.countDown()
+ }
+
+ private fun scheduleNextMainThreadWork(action: () -> Unit) {
+ handler.looper.queue.addIdleHandler {
+ handler.postDelayed(action, INTERVAL_GAP_MS)
+ false
+ }
+ }
+
+ private fun sleepOneSecond() = Thread.sleep(1000)
+ private fun sleepTwoSeconds() = Thread.sleep(2000)
+ private fun sleepThreeSeconds() = Thread.sleep(3000)
+ private fun sleepFiveSeconds() = Thread.sleep(5000)
+
+ private fun assertWithTolerance(msg: String, expected: Int, observed: Int, tolerance: Int) {
+ val abs = kotlin.math.abs(expected - observed)
+ assertTrue("Expected $expected but got $observed. $msg", abs < tolerance)
+ }
+}
diff --git a/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/LogMessageTest.kt b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/LogMessageTest.kt
new file mode 100644
index 0000000000..8c6e0a4a3c
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/LogMessageTest.kt
@@ -0,0 +1,136 @@
+package io.embrace.android.embracesdk
+
+import com.google.gson.stream.JsonReader
+import io.embrace.android.embracesdk.comms.delivery.DeliveryFailedApiCalls
+import io.embrace.android.embracesdk.internal.EmbraceSerializer
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.io.IOException
+
+@Suppress("DEPRECATION")
+internal class LogMessageTest : BaseTest() {
+
+ @Before
+ fun setup() {
+ startEmbraceInForeground()
+ }
+
+ @After
+ fun tearDown() {
+ sendBackground()
+ }
+
+ @Test
+ fun logInfoTest() {
+ Embrace.getInstance().logInfo("Test log info")
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-info-event.json")
+ }
+ }
+
+ @Test
+ fun logInfoWithPropertyTest() {
+ val properties = HashMap()
+ properties["info"] = "test property"
+
+ Embrace.getInstance().logMessage("Test log info with property", Severity.INFO, properties)
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-info-with-property-event.json")
+ }
+ }
+
+ @Test
+ fun logInfoFailRequestTest() {
+ waitForFailedRequest(
+ endpoint = EmbraceEndpoint.LOGGING,
+ request = { Embrace.getInstance().logInfo("Test log info fail") },
+ action = {
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-info-fail-event.json")
+ }
+ },
+ validate = { file -> validateFileContent(file) }
+ )
+ }
+
+ private fun validateFileContent(file: File) {
+ try {
+ assertTrue(file.exists() && !file.isDirectory)
+ readFile(file, "/v1/log/logging")
+ val serializer = EmbraceSerializer()
+ file.bufferedReader().use { bufferedReader ->
+ JsonReader(bufferedReader).use { jsonreader ->
+ jsonreader.isLenient = true
+ val obj = serializer.loadObject(jsonreader, DeliveryFailedApiCalls::class.java)
+ if (obj != null) {
+ val failedCallFileName = obj.element().cachedPayload
+ assert(failedCallFileName.isNotBlank())
+ readFileContent("Test log info fail", failedCallFileName)
+ } else {
+ fail("Null object")
+ }
+ }
+ }
+ } catch (e: IOException) {
+ fail("IOException error: ${e.message}")
+ }
+ }
+
+ @Test
+ fun logErrorTest() {
+ Embrace.getInstance().logError("Test log error")
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-error-event.json")
+ }
+ }
+
+ @Test
+ fun logErrorWithPropertyTest() {
+ val properties = HashMap()
+ properties["error"] = "test property"
+
+ Embrace.getInstance().logMessage("Test log error", Severity.ERROR, properties)
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-error-with-property-event.json")
+ }
+ }
+
+ @Test
+ fun logExceptionTest() {
+ Embrace.getInstance().logException(Exception("Another log error"))
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-error-with-exception-event.json")
+ }
+ }
+
+ @Test
+ fun logErrorWithExceptionAndMessageTest() {
+ val exception = java.lang.NullPointerException("Exception message")
+ Embrace.getInstance().logException(exception, Severity.ERROR, mapOf(), "log message")
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(
+ request,
+ "log-error-with-exception-and-message-event.json"
+ )
+ }
+ }
+
+ @Test
+ fun logWarningTest() {
+ Embrace.getInstance().logWarning("Test log warning")
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "log-warning-event.json")
+ }
+ }
+}
diff --git a/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/MomentMessageTest.kt b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/MomentMessageTest.kt
new file mode 100644
index 0000000000..737a6d0522
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/MomentMessageTest.kt
@@ -0,0 +1,126 @@
+package io.embrace.android.embracesdk
+
+import com.google.gson.stream.JsonReader
+import io.embrace.android.embracesdk.comms.delivery.DeliveryFailedApiCalls
+import io.embrace.android.embracesdk.internal.EmbraceSerializer
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.io.IOException
+
+private const val MOMENT_NAME = "my_moment"
+
+internal class MomentMessageTest : BaseTest() {
+
+ @Before
+ fun setup() {
+ startEmbraceInForeground()
+ }
+
+ @After
+ fun tearDown() {
+ sendBackground()
+ }
+
+ /**
+ * Verifies that a custom moment is sent by the SDK.
+ */
+ @Test
+ fun customMomentTest() {
+ // Send start moment
+ Embrace.getInstance().startMoment(MOMENT_NAME)
+
+ // Validate start moment request
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "moment-custom-start-event.json")
+ }
+
+ // Send end moment
+ Embrace.getInstance().endMoment(MOMENT_NAME)
+
+ // Validate end moment request
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "moment-custom-end-event.json")
+ }
+ }
+
+ /**
+ * Verifies that a custom moment with properties is sent by the SDK.
+ */
+ @Test
+ fun startMomentWithPropertiesTest() {
+ // ignore startup event
+ Embrace.getImpl().endAppStartup(null)
+ waitForRequest()
+
+ val properties = HashMap()
+ properties["key1"] = "value1"
+ properties["key2"] = "value2"
+
+ // Send start moment with properties
+ Embrace.getInstance().startMoment(MOMENT_NAME, MOMENT_NAME, properties)
+
+ // Validate start moment request with properties
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(
+ request,
+ "moment-custom-with-properties-start-event.json"
+ )
+ }
+
+ // Send end moment
+ Embrace.getInstance().endMoment(MOMENT_NAME)
+
+ // Validate end moment request
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(
+ request,
+ "moment-custom-with-properties-end-event.json"
+ )
+ }
+
+ }
+
+ /**
+ * Verifies that a custom moment is sent by the SDK.
+ */
+ @Test
+ fun customMomentFailRequestTest() {
+ waitForFailedRequest(
+ endpoint = EmbraceEndpoint.EVENTS,
+ request = { Embrace.getInstance().startMoment(MOMENT_NAME) },
+ action = {
+ // Validate start moment request
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "moment-custom-start-event.json")
+ }
+ },
+ validate = { file -> validateFileContent(file) })
+ }
+
+ private fun validateFileContent(file: File) {
+ try {
+ assertTrue(file.exists() && !file.isDirectory)
+ readFile(file, EmbraceEndpoint.EVENTS.url)
+ val serializer = EmbraceSerializer()
+ file.bufferedReader().use { bufferedReader ->
+ JsonReader(bufferedReader).use { jsonreader ->
+ jsonreader.isLenient = true
+ val obj = serializer.loadObject(jsonreader, DeliveryFailedApiCalls::class.java)
+ if (obj != null) {
+ val failedCallFileName = obj.element().cachedPayload
+ assert(failedCallFileName.isNotBlank())
+ readFileContent("\"t\":\"start\"", failedCallFileName)
+ } else {
+ fail("Null object")
+ }
+ }
+ }
+ } catch (e: IOException) {
+ fail("IOException error: ${e.message}")
+ }
+ }
+}
diff --git a/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/SessionMessageTest.kt b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/SessionMessageTest.kt
new file mode 100644
index 0000000000..86bdae0b5c
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/java/io/embrace/android/embracesdk/SessionMessageTest.kt
@@ -0,0 +1,35 @@
+package io.embrace.android.embracesdk
+
+import org.junit.Before
+import org.junit.Test
+
+internal class SessionMessageTest : BaseTest() {
+
+ @Before
+ fun setup() {
+ startEmbraceInForeground()
+ }
+
+ /**
+ * Verifies that a session end message is sent.
+ */
+ @Test
+ fun sessionEndMessageTest() {
+ addCoreWebVitals()
+ sendBackground()
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "moment-startup-end-event.json")
+ }
+
+ waitForRequest { request ->
+ validateMessageAgainstGoldenFile(request, "session-end.json")
+ }
+ }
+
+ private fun addCoreWebVitals() {
+ val webViewExpectedLog =
+ mContext.assets.open("golden-files/${"expected-webview-core-vital.json"}").bufferedReader().readText()
+ Embrace.getInstance().trackWebViewPerformance("myWebView", webViewExpectedLog)
+ }
+}
diff --git a/embrace-android-sdk/src/androidTest/res/layout/web_view_activity.xml b/embrace-android-sdk/src/androidTest/res/layout/web_view_activity.xml
new file mode 100644
index 0000000000..98cc24c7ad
--- /dev/null
+++ b/embrace-android-sdk/src/androidTest/res/layout/web_view_activity.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/embrace-android-sdk/src/androidTest/res/raw/mparticle_js_sdk b/embrace-android-sdk/src/androidTest/res/raw/mparticle_js_sdk
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/NullParametersTest.java b/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/NullParametersTest.java
new file mode 100644
index 0000000000..f67880c740
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/NullParametersTest.java
@@ -0,0 +1,323 @@
+package io.embrace.android.embracesdk;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static io.embrace.android.embracesdk.Embrace.NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE;
+import static io.embrace.android.embracesdk.assertions.InternalErrorAssertionsKt.assertInternalErrorLogged;
+
+import android.webkit.ConsoleMessage;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.SocketException;
+import java.util.Map;
+
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest;
+import io.embrace.android.embracesdk.spans.EmbraceSpan;
+import io.embrace.android.embracesdk.spans.ErrorCode;
+
+/**
+ * TODO: add a lint rule to verify that all public API methods that have @NonNull parameters have a corresponding test here
+ */
+@SuppressWarnings("DataFlowIssue")
+@RunWith(AndroidJUnit4.class)
+public class NullParametersTest {
+ private static final SocketException EXCEPTION = new SocketException();
+
+ private static final String NULL_STRING = null;
+
+ @Rule
+ public IntegrationTestRule testRule = new IntegrationTestRule();
+
+ @NonNull
+ private final Embrace embrace = testRule.getEmbrace();
+
+ @Before
+ public void before() {
+ assertTrue(embrace.isStarted());
+ }
+
+ @Test
+ public void testAddSessionProperty() {
+ embrace.addSessionProperty(null, "value", false);
+ assertError("addSessionProperty");
+ embrace.addSessionProperty("key", null, false);
+ assertError("addSessionProperty");
+ }
+
+ @Test
+ public void testAddUserPersona() {
+ embrace.addUserPersona(null);
+ assertError("addUserPersona");
+ }
+
+ @Test
+ public void testClearUserPersona() {
+ embrace.clearUserPersona(null);
+ assertError("clearUserPersona");
+ }
+
+ @Test
+ public void testRemoveSessionProperty() {
+ embrace.removeSessionProperty(null);
+ assertError("removeSessionProperty");
+ }
+
+ @Test
+ public void testStartMoment1Parameter() {
+ embrace.startMoment(null);
+ assertError("startMoment");
+ }
+
+ @Test
+ public void testStartMoment2Parameters() {
+ embrace.startMoment(null, null);
+ assertError("startMoment");
+ }
+
+ @Test
+ public void testStartMoment3ParametersAllowProperties() {
+ embrace.startMoment(null, null, null);
+ assertError("startMoment");
+ }
+
+ @Test
+ public void testEndMoment1Parameter() {
+ embrace.endMoment(null);
+ assertError("endMoment");
+ }
+
+ @Test
+ public void testEndMoment2ParametersCustomIdentifier() {
+ embrace.endMoment(null, NULL_STRING);
+ assertError("endMoment");
+ }
+
+ @Test
+ public void testEndMoment2ParametersAllowProperties() {
+ embrace.endMoment(null, NULL_STRING);
+ assertError("endMoment");
+ }
+
+ @Test
+ public void testEndMoment3Parameters() {
+ embrace.endMoment(null, NULL_STRING, null);
+ assertError("endMoment");
+ }
+
+ @Test
+ public void testEndAppStartup() {
+ embrace.endAppStartup(null);
+ assertError("endAppStartup");
+ }
+
+ @Test
+ public void testRecordNetworkRequest() {
+ EmbraceNetworkRequest request = null;
+ embrace.recordNetworkRequest(request);
+ assertError("recordNetworkRequest");
+ }
+
+ @Test
+ public void testLogInfo() {
+ embrace.logInfo(null);
+ assertError("logInfo");
+ }
+
+ @Test
+ public void testLogWarning() {
+ embrace.logWarning(null);
+ assertError("logWarning");
+ }
+
+ @Test
+ public void testLogError() {
+ embrace.logError(NULL_STRING);
+ assertError("logError");
+ }
+
+ @Test
+ public void testLogException() {
+ embrace.logException(null);
+ assertError("logException");
+ }
+
+ @Test
+ public void testLogException2Parameters() {
+ embrace.logException(null, Severity.ERROR);
+ assertError("logException");
+ embrace.logException(EXCEPTION, null);
+ assertError("logException");
+ }
+
+ @Test
+ public void testLogException3Parameters() {
+ embrace.logException(null, Severity.ERROR, null);
+ assertError("logException");
+ embrace.logException(EXCEPTION, null, null);
+ assertError("logException");
+ }
+
+ @Test
+ public void testLogException4Parameters() {
+ embrace.logException(null, Severity.ERROR, null, null);
+ assertError("logException");
+ embrace.logException(EXCEPTION, null, null, null);
+ assertError("logException");
+ }
+
+ @Test
+ public void testLogCustomStacktrace() {
+ embrace.logCustomStacktrace(null);
+ assertError("logCustomStacktrace");
+ }
+
+ @Test
+ public void testLogCustomStacktrace2Parameters() {
+ embrace.logCustomStacktrace(null, Severity.ERROR);
+ assertError("logCustomStacktrace");
+ embrace.logCustomStacktrace(new StackTraceElement[0], null);
+ assertError("logCustomStacktrace");
+ }
+
+ @Test
+ public void testLogCustomStacktrace3Parameters() {
+ embrace.logCustomStacktrace(null, Severity.ERROR, null);
+ assertError("logCustomStacktrace");
+ embrace.logCustomStacktrace(new StackTraceElement[0], null, null);
+ assertError("logCustomStacktrace");
+ }
+
+ @Test
+ public void testLogCustomStacktrace4Parameters() {
+ embrace.logCustomStacktrace(null, Severity.ERROR, null, null);
+ assertError("logCustomStacktrace");
+ embrace.logCustomStacktrace(new StackTraceElement[0], null, null, null);
+ assertError("logCustomStacktrace");
+ }
+
+ @Test
+ public void testStartView() {
+ embrace.startView(null);
+ assertError("startView");
+ }
+
+ @Test
+ public void testEndView() {
+ embrace.endView(null);
+ assertError("endView");
+ }
+
+ @Test
+ public void testAddBreadcrumb() {
+ embrace.addBreadcrumb(null);
+ assertError("addBreadcrumb");
+ }
+
+ @Test
+ public void testLogPushNotification() {
+ embrace.logPushNotification(null, null, null, null, null, null, true, true);
+ assertError("logPushNotification");
+ embrace.logPushNotification(null, null, null, null, null, 1, null, true);
+ assertError("logPushNotification");
+ embrace.logPushNotification(null, null, null, null, null, 1, true, null);
+ assertError("logPushNotification");
+ }
+
+ @Test
+ public void testTrackWebViewPerformanceWithStringMessage() {
+ embrace.trackWebViewPerformance(null, "message");
+ assertError("trackWebViewPerformance");
+ embrace.trackWebViewPerformance("tag", NULL_STRING);
+ assertError("trackWebViewPerformance");
+ }
+
+ @Test
+ public void testTrackWebViewPerformanceWithConsoleMessage() {
+ embrace.trackWebViewPerformance(null, new ConsoleMessage("message", "id", 1, ConsoleMessage.MessageLevel.DEBUG));
+ assertError("trackWebViewPerformance");
+ embrace.trackWebViewPerformance("tag", (ConsoleMessage) null);
+ assertError("trackWebViewPerformance");
+ }
+
+ @Test
+ public void testCreateSpan() {
+ assertNull(embrace.createSpan(null));
+ assertError("createSpan");
+ }
+
+ @Test
+ public void testCreateSpanWithParent() {
+ assertNull(embrace.createSpan(null, null));
+ assertError("createSpan");
+ }
+
+ @Test
+ public void testRecordSpan() {
+ assertTrue(embrace.recordSpan(null, () -> true));
+ assertError("recordSpan");
+ assertNull(embrace.recordSpan("test-span", null));
+ assertError("recordSpan");
+ }
+
+ @Test
+ public void testRecordSpan3Parameters() {
+ assertTrue(embrace.recordSpan(null, null, () -> true));
+ assertError("recordSpan");
+ assertNull(embrace.recordSpan("test-span", null, null));
+ assertError("recordSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpan() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1));
+ assertError("recordCompletedSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpanWithErrorCode() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1, (ErrorCode) null));
+ assertError("recordCompletedSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpanWithParent() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1, (EmbraceSpan) null));
+ assertError("recordCompletedSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpanWithErrorCodeAndParent() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1, (ErrorCode) null, null));
+ assertError("recordCompletedSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpanWithAttributesAndEvents() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1, (Map) null, null));
+ assertError("recordCompletedSpan");
+ }
+
+ @Test
+ public void testRecordCompletedSpanWithEverything() {
+ assertFalse(embrace.recordCompletedSpan(null, 0, 1, null, null, null, null));
+ assertError("recordCompletedSpan");
+ }
+
+ private void assertError(@NonNull String functionName) {
+ assertInternalErrorLogged(
+ IntegrationTestRuleExtensionsKt.exceptionsService().getCurrentExceptionError(),
+ IllegalArgumentException.class.getCanonicalName(),
+ functionName + NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE,
+ IntegrationTestRule.DEFAULT_SDK_START_TIME_MS
+ );
+ IntegrationTestRuleExtensionsKt.exceptionsService().resetExceptionErrorObject();
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/PreSdkStartTest.java b/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/PreSdkStartTest.java
new file mode 100644
index 0000000000..fb0a0a98ca
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/java/io/embrace/android/embracesdk/PreSdkStartTest.java
@@ -0,0 +1,41 @@
+package io.embrace.android.embracesdk;
+
+import static org.junit.Assert.assertFalse;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PreSdkStartTest {
+
+ @Rule
+ public IntegrationTestRule testRule = new IntegrationTestRule(
+ () -> IntegrationTestRule.Companion.newHarness(false)
+ );
+
+ @NonNull
+ private final Embrace embrace = testRule.getEmbrace();
+
+ @Test
+ public void testStartWithNullContext() {
+ embrace.start(null);
+ embrace.start(null, true);
+ embrace.start(null, false, Embrace.AppFramework.NATIVE);
+ assertFalse(embrace.isStarted());
+ }
+
+ @Test
+ public void testStartWithNullAppFramework() {
+ embrace.start(testRule.harness.getFakeCoreModule().getContext(), false, null);
+ assertFalse(embrace.isStarted());
+ }
+
+ @Test
+ public void testSetAppId() {
+ assertFalse(embrace.setAppId(null));
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRule.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRule.kt
new file mode 100644
index 0000000000..c2f90134f5
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRule.kt
@@ -0,0 +1,208 @@
+package io.embrace.android.embracesdk
+
+import io.embrace.android.embracesdk.IntegrationTestRule.Harness
+import io.embrace.android.embracesdk.config.local.LocalConfig
+import io.embrace.android.embracesdk.config.local.NetworkLocalConfig
+import io.embrace.android.embracesdk.config.local.SdkLocalConfig
+import io.embrace.android.embracesdk.config.remote.RemoteConfig
+import io.embrace.android.embracesdk.config.remote.SpansRemoteConfig
+import io.embrace.android.embracesdk.fakes.FakeClock
+import io.embrace.android.embracesdk.fakes.FakeConfigService
+import io.embrace.android.embracesdk.fakes.fakeNetworkBehavior
+import io.embrace.android.embracesdk.fakes.fakeSdkModeBehavior
+import io.embrace.android.embracesdk.fakes.fakeSpansBehavior
+import io.embrace.android.embracesdk.fakes.injection.FakeCoreModule
+import io.embrace.android.embracesdk.fakes.injection.FakeDeliveryModule
+import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
+import io.embrace.android.embracesdk.injection.AndroidServicesModule
+import io.embrace.android.embracesdk.injection.AndroidServicesModuleImpl
+import io.embrace.android.embracesdk.injection.CoreModule
+import io.embrace.android.embracesdk.injection.DataCaptureServiceModule
+import io.embrace.android.embracesdk.injection.DataCaptureServiceModuleImpl
+import io.embrace.android.embracesdk.injection.DeliveryModule
+import io.embrace.android.embracesdk.injection.EssentialServiceModule
+import io.embrace.android.embracesdk.injection.EssentialServiceModuleImpl
+import io.embrace.android.embracesdk.injection.InitModule
+import io.embrace.android.embracesdk.injection.SystemServiceModule
+import io.embrace.android.embracesdk.injection.SystemServiceModuleImpl
+import io.embrace.android.embracesdk.internal.BuildInfo
+import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
+import io.embrace.android.embracesdk.worker.WorkerThreadModule
+import io.embrace.android.embracesdk.worker.WorkerThreadModuleImpl
+import org.junit.rules.ExternalResource
+
+/**
+ * A [org.junit.Rule] that is responsible for setting up and tearing down the Embrace SDK for use in
+ * component/integration testing. Test cases must be run with a test runner that is compatible with Robolectric.
+ *
+ * The SDK instance exposed is almost like the one used in production other than the 3 modules that have
+ * to faked at least in part in order for this to be useful in tests:
+ *
+ * 1) [CoreModule]: Requires [FakeCoreModule] to fake Android system objects like [Application], [Context],
+ * and resources that are normally not available in a JUnit test environment. Also, a [FakeClock] is used
+ * so we can control time to control timing and execution manually rather than waiting for normal
+ * time to pass.
+ *
+ * 2) [EssentialServiceModule]: Requires an instance of [EssentialServiceModuleImpl] that uses a [FakeConfigService]
+ * to allow the appropriate feature flags be set to suit the test case. Everything else in the
+ * module is otherwise real unless provided by [FakeCoreModule]
+ *
+ * 3) [DeliveryModule]: Requires [FakeDeliveryModule] so that a [FakeDeliveryService] can be used in order
+ * for payloads normally sent to the server to be inspected to verify the correctness of the data produced.
+ * Everything else in [FakeDeliveryModule] is otherwise real unless provided by [FakeCoreModule]
+ *
+ * The modules instantiated by default reference each other, e.g. the same instance of [FakeCoreModule] is used
+ * in all the other modules. This means modifications of one object within a module will be reflected in the
+ * other modules, e.g. [InitModule.clock]. This allows the system to behave as one unit.
+ *
+ * While it is possible to override the default behavior of the [Embrace] instance in the Rule by passing in
+ * a custom supplier to create an instance of [Harness] with custom overridden constructor parameters, be
+ * careful when you do so by passing in overridden module instances as that might break the integrity of the
+ * "one instance" guarantee of using the modules created by default (besides the fakes).
+ *
+ * It is also possible to access internal modules & dependencies that are not exposed via the public API.
+ * For example, it is possible to access the [FakeDeliveryModule] to get event/session payloads the SDK sent.
+ *
+ * In general, it is recommended to use functions declared in IntegrationTestRuleExtensions.kt that retrieve
+ * this useful information for you. If it's not possible to get information from the SDK and you
+ * need it for a test, please consider adding a new function to IntegrationTestRuleExtensions.kt so that others
+ * find it easier to write tests in the future.
+ *
+ * Because there are parts of the [Embrace] instance being tested that are using fakes, unless you are careful,
+ * do not use this to verify code paths that are overridden by fakes. For example, do not use this rule to
+ * verify the default config settings because what's in use is a [FakeConfigService], which is not used in
+ * production.
+ */
+internal class IntegrationTestRule(
+ private val harnessSupplier: () -> Harness = { Harness() }
+) : ExternalResource() {
+ /**
+ * The [Embrace] instance that can be used for testing
+ */
+ val embrace = Embrace.getInstance()
+
+ /**
+ * Instance of the test harness that is recreating on every test iteration
+ */
+ lateinit var harness: Harness
+
+ /**
+ * Setup the Embrace SDK so it's ready for testing.
+ */
+ override fun before() {
+ harness = harnessSupplier.invoke()
+ with(harness) {
+ val embraceImpl = EmbraceImpl(
+ { initModule },
+ { _, _ -> fakeCoreModule },
+ { workerThreadModule },
+ { _ -> systemServiceModule },
+ { _, _, _ -> androidServicesModule },
+ { _, _, _, _, _, _, _, _, _, _, _ -> essentialServiceModule },
+ { _, _, _, _, _ -> dataCaptureServiceModule },
+ { _, _, _, _, _ -> fakeDeliveryModule }
+ )
+ Embrace.setImpl(embraceImpl)
+ if (startImmediately) {
+ embrace.start(fakeCoreModule.context, enableIntegrationTesting, appFramework)
+ }
+ }
+ }
+
+ /**
+ * Teardown the Embrace SDK, closing any resources as required
+ */
+ override fun after() {
+ InternalStaticEmbraceLogger.logger.setToDefault()
+ Embrace.getImpl().stop()
+ }
+
+ /**
+ * Test harness for which an instance is generated each test run and provided to the test by the Rule
+ */
+ internal class Harness(
+ currentTimeMs: Long = DEFAULT_SDK_START_TIME_MS,
+ val fakeClock: FakeClock = FakeClock(currentTime = currentTimeMs),
+ val enableIntegrationTesting: Boolean = false,
+ val appFramework: Embrace.AppFramework = Embrace.AppFramework.NATIVE,
+ val initModule: InitModule = FakeInitModule(clock = fakeClock),
+ val fakeCoreModule: FakeCoreModule = FakeCoreModule(),
+ val workerThreadModule: WorkerThreadModule = WorkerThreadModuleImpl(),
+ val fakeConfigService: FakeConfigService = FakeConfigService(
+ backgroundActivityCaptureEnabled = true,
+ sdkModeBehavior = fakeSdkModeBehavior(
+ isDebug = fakeCoreModule.isDebug,
+ localCfg = { DEFAULT_LOCAL_CONFIG }
+ ),
+ networkBehavior = fakeNetworkBehavior(
+ localCfg = { DEFAULT_SDK_LOCAL_CONFIG },
+ remoteCfg = { DEFAULT_SDK_REMOTE_CONFIG }
+ ),
+ spansBehavior = fakeSpansBehavior {
+ SpansRemoteConfig(pctEnabled = 100f)
+ }
+ ),
+ val systemServiceModule: SystemServiceModule =
+ SystemServiceModuleImpl(
+ coreModule = fakeCoreModule
+ ),
+ val androidServicesModule: AndroidServicesModule = AndroidServicesModuleImpl(
+ initModule = initModule,
+ coreModule = fakeCoreModule,
+ workerThreadModule = workerThreadModule,
+ ),
+ val essentialServiceModule: EssentialServiceModule =
+ EssentialServiceModuleImpl(
+ initModule = initModule,
+ coreModule = fakeCoreModule,
+ workerThreadModule = workerThreadModule,
+ systemServiceModule = systemServiceModule,
+ androidServicesModule = androidServicesModule,
+ buildInfo = BuildInfo.fromResources(fakeCoreModule.resources, fakeCoreModule.context.packageName),
+ customAppId = null,
+ enableIntegrationTesting = enableIntegrationTesting,
+ configStopAction = { Embrace.getImpl().stop() },
+ configServiceProvider = { fakeConfigService }
+ ),
+ val dataCaptureServiceModule: DataCaptureServiceModule =
+ DataCaptureServiceModuleImpl(
+ initModule = initModule,
+ coreModule = fakeCoreModule,
+ systemServiceModule = systemServiceModule,
+ essentialServiceModule = essentialServiceModule,
+ workerThreadModule = workerThreadModule
+ ),
+ val fakeDeliveryModule: FakeDeliveryModule =
+ FakeDeliveryModule(
+ initModule = initModule,
+ coreModule = fakeCoreModule,
+ essentialServiceModule = essentialServiceModule,
+ dataCaptureServiceModule = dataCaptureServiceModule,
+ workerThreadModule = workerThreadModule
+ ),
+ val startImmediately: Boolean = true
+ )
+
+ companion object {
+ const val DEFAULT_SDK_START_TIME_MS = 1692201600L
+
+ fun newHarness(startImmediately: Boolean) = Harness(startImmediately = startImmediately)
+
+ private val DEFAULT_SDK_LOCAL_CONFIG = SdkLocalConfig(
+ networking = NetworkLocalConfig(
+ enableNativeMonitoring = false
+ ),
+ betaFeaturesEnabled = false
+ )
+
+ private val DEFAULT_SDK_REMOTE_CONFIG = RemoteConfig(
+ disabledUrlPatterns = setOf("dontlogmebro.pizza")
+ )
+
+ val DEFAULT_LOCAL_CONFIG = LocalConfig(
+ appId = "CoYh3",
+ ndkEnabled = false,
+ sdkConfig = DEFAULT_SDK_LOCAL_CONFIG
+ )
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRuleExtensions.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRuleExtensions.kt
new file mode 100644
index 0000000000..3615134763
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/IntegrationTestRuleExtensions.kt
@@ -0,0 +1,107 @@
+package io.embrace.android.embracesdk
+
+import io.embrace.android.embracesdk.logging.EmbraceInternalErrorService
+import io.embrace.android.embracesdk.payload.EventMessage
+import io.embrace.android.embracesdk.payload.SessionMessage
+import org.junit.Assert.assertEquals
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+/*** Extension functions that are syntactic sugar for retrieving information from the SDK. ***/
+
+/**
+ * Returns a list of [EventMessage] logs that were sent by the SDK since startup. If [expectedSize] is specified, it will wait up to
+ * 1 second to validate the number of sent log message equal that size. If a second passes that the size requirement is not met, a
+ * [TimeoutException] will be thrown. If [expectedSize] is null or not specified, the correct sent log messages will be returned right
+ * away.
+ */
+internal fun IntegrationTestRule.Harness.getSentLogMessages(expectedSize: Int? = null): List {
+ val logs = fakeDeliveryModule.deliveryService.lastSentLogs
+ return when (expectedSize) {
+ null -> logs
+ else -> returnIfConditionMet({ logs }) {
+ logs.size == expectedSize
+ }
+ }
+}
+
+/**
+ * Returns the last [EventMessage] log that was sent by the SDK. If [expectedSize] is specified, it will wait up to 1 second to validate
+ * the number of sent log message equal that size. If a second passes that the size requirement is not met, a [TimeoutException] will
+ * be thrown. If [expectedSize] is null or not specified, the correct sent log messages will be returned right away.
+ */
+internal fun IntegrationTestRule.Harness.getLastSentLogMessage(expectedSize: Int? = null): EventMessage {
+ return getSentLogMessages(expectedSize).last()
+}
+
+/**
+ * Returns a list of [SessionMessage] that were sent by the SDK since startup.
+ */
+internal fun IntegrationTestRule.Harness.getSentSessionMessages(): List {
+ return fakeDeliveryModule.deliveryService.lastSentSessions.map { it.first }
+}
+
+/**
+ * Returns the last [SessionMessage] that was sent by the SDK.
+ */
+internal fun IntegrationTestRule.Harness.getLastSentSessionMessage(): SessionMessage {
+ return getSentSessionMessages().last()
+}
+
+/**
+ * Starts & ends a session for the purposes of testing. An action can be supplied as a lambda
+ * parameter: any code inside the lambda will be executed, so can be used to add breadcrumbs,
+ * send log messages etc, while the session is active. The end session message is returned so
+ * that the caller can perform further assertions if needed.
+ *
+ * This function fakes the lifecycle events that trigger a session start & end. The session
+ * should always be 30s long. Additionally, it performs assertions against fields that
+ * are guaranteed not to change in the start/end message.
+ */
+internal fun IntegrationTestRule.Harness.recordSession(action: () -> Unit): SessionMessage {
+ // get the activity service & simulate the lifecycle event that triggers a new session.
+ val activityService = checkNotNull(Embrace.getImpl().activityService)
+ activityService.onForeground()
+
+ // assert a session was started.
+ val startSession = getLastSentSessionMessage()
+ assertEquals("st", startSession.session.messageType)
+ // TODO: future: increase number of assertions on what is always in a start message?
+
+ // perform a custom action during the session boundary, e.g. adding a breadcrumb.
+ action()
+
+ // end session 30s later by entering background
+ fakeClock.tick(30000)
+ activityService.onBackground()
+
+ val endSession = getLastSentSessionMessage()
+ assertEquals("en", endSession.session.messageType)
+ // TODO: future: increase number of assertions on what is always in a start message?
+
+ // return the session end message for further assertions.
+ return endSession
+}
+
+internal fun exceptionsService(): EmbraceInternalErrorService? = Embrace.getImpl().exceptionsService
+
+/**
+ * Return the result of [desiredValueSupplier] if [condition] is true before [waitTimeMs] elapses. Otherwise, throws [TimeoutException]
+ */
+internal fun returnIfConditionMet(desiredValueSupplier: () -> T, waitTimeMs: Int = 1000, condition: () -> Boolean): T {
+ val tries: Int = waitTimeMs / CHECK_INTERVAL_MS
+ val countDownLatch = CountDownLatch(1)
+
+ repeat(tries) {
+ if (!condition()) {
+ countDownLatch.await(CHECK_INTERVAL_MS.toLong(), TimeUnit.MILLISECONDS)
+ } else {
+ return desiredValueSupplier.invoke()
+ }
+ }
+
+ throw TimeoutException("Timeout period elapsed before condition met")
+}
+
+private const val CHECK_INTERVAL_MS: Int = 10
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/SessionApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/SessionApiTest.kt
new file mode 100644
index 0000000000..bfa964e3b4
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/SessionApiTest.kt
@@ -0,0 +1,33 @@
+package io.embrace.android.embracesdk
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test for the internal implementation of the Embrace SDK
+ */
+@RunWith(AndroidJUnit4::class)
+internal class SessionApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule()
+
+ @Test
+ fun `session messages are recorded`() {
+ with(testRule) {
+ assertTrue(harness.getSentSessionMessages().isEmpty())
+
+ val session = harness.recordSession {
+ embrace.addBreadcrumb("Hello, World!")
+ }
+
+ // perform further assertions that only apply to this individual test case.
+ val crumb = checkNotNull(session.breadcrumbs?.customBreadcrumbs?.single())
+ assertEquals("Hello, World!", crumb.message)
+ }
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/InternalErrorAssertions.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/InternalErrorAssertions.kt
new file mode 100644
index 0000000000..a339acc804
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/InternalErrorAssertions.kt
@@ -0,0 +1,43 @@
+package io.embrace.android.embracesdk.assertions
+
+import io.embrace.android.embracesdk.payload.ExceptionError
+import io.embrace.android.embracesdk.IntegrationTestRule
+import org.junit.Assert.assertTrue
+
+/**
+ * Return true if at least one exception matching the expected time, exception type, and error message is found in the internal errors
+ */
+internal fun assertInternalErrorLogged(
+ exceptionError: ExceptionError?,
+ exceptionClassName: String,
+ errorMessage: String,
+ errorTimeMs: Long = IntegrationTestRule.DEFAULT_SDK_START_TIME_MS
+) {
+ requireNotNull(exceptionError) { "No internal errors found" }
+ var foundErrorMatch = false
+ var foundErrorAtTime = false
+ val unmatchedDetails: MutableList = mutableListOf()
+ val errors = exceptionError.exceptionErrors.toList()
+ assertTrue("No exception errors found", errors.isNotEmpty())
+ errors.forEach { error ->
+ if (errorTimeMs == error.timestamp) {
+ foundErrorAtTime = true
+ val firstExceptionInfo = checkNotNull(error.exceptions).first()
+ with(firstExceptionInfo) {
+ if (exceptionClassName == name && errorMessage == message) {
+ foundErrorMatch = true
+ } else {
+ unmatchedDetails.add("'$exceptionClassName' is not '$name' OR '$errorMessage' is not '$message' \n")
+ }
+ }
+ }
+ }
+
+ assertTrue("No internal error found matching the expected time", foundErrorAtTime)
+
+ assertTrue(
+ "Expected exception not found. " +
+ "Found following ${unmatchedDetails.size} exceptions in ${errors.size} errors instead: $unmatchedDetails",
+ foundErrorMatch
+ )
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/LogMessageAssertions.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/LogMessageAssertions.kt
new file mode 100644
index 0000000000..14c0140c2e
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/LogMessageAssertions.kt
@@ -0,0 +1,40 @@
+package io.embrace.android.embracesdk.assertions
+
+import io.embrace.android.embracesdk.Embrace
+import io.embrace.android.embracesdk.EmbraceEvent
+import io.embrace.android.embracesdk.payload.EventMessage
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.LogExceptionType
+import org.junit.Assert.assertEquals
+
+/**
+ * Asserts that a log message was sent with the given parameters.
+ */
+internal fun assertLogMessageReceived(
+ eventMessage: EventMessage,
+ message: String,
+ eventType: EmbraceEvent.Type,
+ logType: LogExceptionType = LogExceptionType.NONE,
+ timeMs: Long = IntegrationTestRule.DEFAULT_SDK_START_TIME_MS,
+ properties: Map? = null,
+ exception: Exception? = null,
+ stack: Array? = null
+) {
+ with(eventMessage.event) {
+ assertEquals(message, name)
+ assertEquals(timeMs, timestamp)
+ assertEquals(false, screenshotTaken)
+ assertEquals(logType.value, logExceptionType)
+ assertEquals(eventType, type)
+ assertEquals(Embrace.AppFramework.NATIVE.value, framework)
+ assertEquals(properties, customPropertiesMap)
+ exception?.let {
+ assertEquals(it.message, exceptionMessage)
+ assertEquals(it.javaClass.simpleName, exceptionName)
+ }
+ }
+
+ if (stack != null) {
+ assertEquals(stack.map { it.toString() }, eventMessage.stacktraces?.jvmStacktrace)
+ }
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt
new file mode 100644
index 0000000000..c8b164bb84
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt
@@ -0,0 +1,47 @@
+package io.embrace.android.embracesdk.assertions
+
+import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
+import io.embrace.android.embracesdk.internal.spans.isKey
+import io.embrace.android.embracesdk.internal.spans.isPrivate
+import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
+import io.embrace.android.embracesdk.spans.ErrorCode
+import io.opentelemetry.api.trace.StatusCode
+import org.junit.Assert.assertEquals
+import java.util.concurrent.TimeUnit
+
+/**
+ * Assert the [EmbraceSpanData] is as expected
+ */
+internal fun assertEmbraceSpanData(
+ span: EmbraceSpanData?,
+ expectedStartTimeMs: Long,
+ expectedEndTimeMs: Long,
+ expectedParentId: String,
+ expectedTraceId: String? = null,
+ expectedStatus: StatusCode = StatusCode.OK,
+ errorCode: ErrorCode? = null,
+ expectedCustomAttributes: Map = emptyMap(),
+ expectedEvents: List = emptyList(),
+ private: Boolean = false,
+ key: Boolean = false,
+) {
+ checkNotNull(span)
+ with(span) {
+ assertEquals(TimeUnit.MILLISECONDS.toNanos(expectedStartTimeMs), startTimeNanos)
+ assertEquals(TimeUnit.MILLISECONDS.toNanos(expectedEndTimeMs), endTimeNanos)
+ assertEquals(expectedParentId, parentSpanId)
+ if (expectedTraceId != null) {
+ assertEquals(expectedTraceId, traceId)
+ } else {
+ assertEquals(32, traceId.length)
+ }
+ assertEquals(expectedStatus, status)
+ assertEquals(errorCode?.name, attributes[errorCode?.keyName()])
+ expectedCustomAttributes.forEach { entry ->
+ assertEquals(entry.value, attributes[entry.key])
+ }
+ assertEquals(expectedEvents, events)
+ assertEquals(private, isPrivate())
+ assertEquals(key, isKey())
+ }
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/LoggingApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/LoggingApiTest.kt
new file mode 100644
index 0000000000..4b5bc61c38
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/LoggingApiTest.kt
@@ -0,0 +1,252 @@
+package io.embrace.android.embracesdk.testcases
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.EmbraceEvent
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.LogExceptionType
+import io.embrace.android.embracesdk.Severity
+import io.embrace.android.embracesdk.assertions.assertLogMessageReceived
+import io.embrace.android.embracesdk.getLastSentLogMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.IllegalArgumentException
+
+@RunWith(AndroidJUnit4::class)
+internal class LoggingApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule()
+
+ @Test
+ fun `log info message sent`() {
+ with(testRule) {
+ embrace.logInfo("test message")
+ val eventMessage = harness.getLastSentLogMessage(expectedSize = 1)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "test message",
+ eventType = EmbraceEvent.Type.INFO_LOG
+ )
+ }
+ }
+
+ @Test
+ fun `log warning message sent`() {
+ with(testRule) {
+ embrace.logWarning("test message")
+ val eventMessage = harness.getLastSentLogMessage(expectedSize = 1)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "test message",
+ eventType = EmbraceEvent.Type.WARNING_LOG
+ )
+ }
+ }
+
+ @Test
+ fun `log error message sent`() {
+ with(testRule) {
+ embrace.logError("test message")
+ val eventMessage = harness.getLastSentLogMessage(expectedSize = 1)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "test message",
+ eventType = EmbraceEvent.Type.ERROR_LOG
+ )
+ }
+ }
+
+ @Test
+ fun `log messages with different severities sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ val expectedMessage = "test message ${severity.name}"
+ embrace.logMessage(expectedMessage, severity)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = expectedMessage,
+ eventType = EmbraceEvent.Type.fromSeverity(severity)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log messages with different severities and properties sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ val expectedMessage = "test message ${severity.name}"
+ embrace.logMessage(expectedMessage, severity, customProperties)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = expectedMessage,
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ properties = customProperties
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log exception message sent`() {
+ with(testRule) {
+ embrace.logException(testException)
+ val eventMessage = harness.getLastSentLogMessage(expectedSize = 1)
+ assertLogMessageReceived(
+ eventMessage,
+ message = checkNotNull(testException.message),
+ eventType = EmbraceEvent.Type.ERROR_LOG,
+ logType = LogExceptionType.HANDLED,
+ exception = testException
+ )
+ }
+ }
+
+ @Test
+ fun `log exception with different severities sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ embrace.logException(testException, severity)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = checkNotNull(testException.message),
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ logType = LogExceptionType.HANDLED,
+ exception = testException
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log exception with different severities and properties sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ embrace.logException(testException, severity, customProperties)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = checkNotNull(testException.message),
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ properties = customProperties,
+ logType = LogExceptionType.HANDLED,
+ exception = testException
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log exception with different severities, properties, and custom message sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ val expectedMessage = "test message ${severity.name}"
+ embrace.logException(testException, severity, customProperties, expectedMessage)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = expectedMessage,
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ properties = customProperties,
+ logType = LogExceptionType.HANDLED,
+ exception = testException
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log custom stacktrace message sent`() {
+ with(testRule) {
+ embrace.logCustomStacktrace(stacktrace)
+ val eventMessage = harness.getLastSentLogMessage(expectedSize = 1)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "",
+ eventType = EmbraceEvent.Type.ERROR_LOG,
+ logType = LogExceptionType.HANDLED,
+ stack = stacktrace
+ )
+ }
+ }
+
+ @Test
+ fun `log custom stacktrace with different severities sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ embrace.logCustomStacktrace(stacktrace, severity)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "",
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ logType = LogExceptionType.HANDLED,
+ stack = stacktrace
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log custom stacktrace with different severities and properties sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ embrace.logCustomStacktrace(stacktrace, severity, customProperties)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = "",
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ properties = customProperties,
+ logType = LogExceptionType.HANDLED,
+ stack = stacktrace
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `log custom stacktrace with different severities, properties, and custom message sent`() {
+ var logsSent = 0
+ with(testRule) {
+ Severity.values().forEach { severity ->
+ val expectedMessage = "test message ${severity.name}"
+ embrace.logCustomStacktrace(stacktrace, severity, customProperties, expectedMessage)
+ logsSent++
+ val eventMessage = harness.getLastSentLogMessage(logsSent)
+ assertLogMessageReceived(
+ eventMessage,
+ message = expectedMessage,
+ eventType = EmbraceEvent.Type.fromSeverity(severity),
+ properties = customProperties,
+ logType = LogExceptionType.HANDLED,
+ stack = stacktrace
+ )
+ }
+ }
+ }
+
+ companion object {
+ private val testException = IllegalArgumentException("nooooooo")
+ private val customProperties: Map = linkedMapOf(Pair("first", 1), Pair("second", "two"), Pair("third", true))
+ private val stacktrace = Thread.currentThread().stackTrace
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt
new file mode 100644
index 0000000000..b648d77d20
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt
@@ -0,0 +1,275 @@
+package io.embrace.android.embracesdk.testcases
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
+import io.embrace.android.embracesdk.network.http.HttpMethod
+import io.embrace.android.embracesdk.network.http.NetworkCaptureData
+import io.embrace.android.embracesdk.payload.NetworkCallV2
+import io.embrace.android.embracesdk.recordSession
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import kotlin.math.max
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
+internal class NetworkRequestApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule()
+
+ @Test
+ fun `record basic completed GET request`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ }
+
+ @Test
+ fun `record completed POST request with traceId`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.POST,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200,
+ TRACE_ID,
+ )
+ )
+ }
+
+ @Test
+ fun `record completed request that failed with captured response`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 500,
+ TRACE_ID,
+ NETWORK_CAPTURE_DATA
+ )
+ )
+ }
+
+ @Test
+ fun `record completed request with traceparent`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200,
+ TRACE_ID,
+ TRACEPARENT,
+ NETWORK_CAPTURE_DATA
+ )
+ )
+ }
+
+ @Test
+ fun `record basic incomplete request`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there"
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete POST request with trace ID`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.POST,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete request with network capture`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID,
+ NETWORK_CAPTURE_DATA
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete request with traceparent`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID,
+ TRACEPARENT,
+ NETWORK_CAPTURE_DATA
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `disabled URLs not recorded`() {
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ DISABLED_URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ DISABLED_URL,
+ HttpMethod.GET,
+ START_TIME + 1,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there"
+ )
+ )
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME + 2,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ }
+
+ val networkCall = validateAndReturnExpectedNetworkCall(harness)
+ assertEquals(URL, networkCall.url)
+ }
+ }
+
+ private fun assertSingleNetworkRequestInSession(expectedRequest: EmbraceNetworkRequest, completed: Boolean = true) {
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5L)
+ embrace.recordNetworkRequest(expectedRequest)
+ }
+
+ val networkCall = validateAndReturnExpectedNetworkCall(harness)
+ with(networkCall) {
+ assertEquals(expectedRequest.url, url)
+ assertEquals(expectedRequest.httpMethod, httpMethod)
+ assertEquals(expectedRequest.startTime, startTime)
+ assertEquals(expectedRequest.endTime, endTime)
+ assertEquals(max(expectedRequest.endTime - expectedRequest.startTime, 0L), duration)
+ assertEquals(expectedRequest.traceId, traceId)
+ assertEquals(expectedRequest.w3cTraceparent, w3cTraceparent)
+ if (completed) {
+ assertEquals(expectedRequest.responseCode, responseCode)
+ assertEquals(expectedRequest.bytesSent, bytesSent)
+ assertEquals(expectedRequest.bytesReceived, bytesReceived)
+ assertEquals(null, errorType)
+ assertEquals(null, errorMessage)
+ } else {
+ assertEquals(null, responseCode)
+ assertEquals(0, bytesSent)
+ assertEquals(0, bytesReceived)
+ assertEquals(expectedRequest.errorType, errorType)
+ assertEquals(expectedRequest.errorMessage, errorMessage)
+ }
+ }
+ }
+ }
+
+ private fun validateAndReturnExpectedNetworkCall(harness: IntegrationTestRule.Harness): NetworkCallV2 {
+ val lastSavedSessionRequestCount =
+ harness.fakeDeliveryModule.deliveryService.lastSavedSession?.performanceInfo?.networkRequests?.networkSessionV2?.requests?.size
+ ?: -1
+ val session = harness.fakeDeliveryModule.deliveryService.lastSentSessions[1].first
+ val requests = checkNotNull(session.performanceInfo?.networkRequests?.networkSessionV2?.requests)
+ val requestCount = requests.size
+ val networkCall = requests.first()
+
+ assertEquals(
+ "Unexpected number of requests in sent session: $requestCount. Last saved session requests: $lastSavedSessionRequestCount",
+ 1,
+ requestCount
+ )
+
+ return networkCall
+ }
+
+ companion object {
+ private const val URL = "https://embrace.io"
+ private const val DISABLED_URL = "https://dontlogmebro.pizza/yum"
+ private const val START_TIME = 1692201601L
+ private const val END_TIME = 1692202600L
+ private const val BYTES_SENT = 100L
+ private const val BYTES_RECEIVED = 500L
+ private const val TRACE_ID = "rAnDoM-traceId"
+ private const val TRACEPARENT = "00-c4ada96c31e1b6b9e351a1cffc99ae38-331f3a8acf49d295-01"
+
+ private val NETWORK_CAPTURE_DATA = NetworkCaptureData(
+ requestHeaders = mapOf(Pair("x-emb-test", "holla")),
+ requestQueryParams = "trackMe=noooooo",
+ capturedRequestBody = "haha".toByteArray(),
+ responseHeaders = mapOf(Pair("x-emb-response-header", "alloh")),
+ capturedResponseBody = "woohoo".toByteArray(),
+ dataCaptureErrorMessage = null
+ )
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt
new file mode 100644
index 0000000000..27e242100c
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/PublicApiTest.kt
@@ -0,0 +1,139 @@
+package io.embrace.android.embracesdk.testcases
+
+import android.os.Build.VERSION_CODES.TIRAMISU
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.Embrace.AppFramework
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.internal.ApkToolsConfig
+import io.embrace.android.embracesdk.internal.TraceparentGeneratorTest.Companion.validPattern
+import io.embrace.android.embracesdk.recordSession
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+/**
+ * Validation of the basic and miscellaneous functionality of the Android SDK
+ */
+@Config(sdk = [TIRAMISU])
+@RunWith(AndroidJUnit4::class)
+internal class PublicApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule(
+ harnessSupplier = {
+ IntegrationTestRule.newHarness(startImmediately = false)
+ }
+ )
+
+ @Before
+ fun before() {
+ ApkToolsConfig.IS_SDK_DISABLED = false
+ }
+
+ @Test
+ fun `SDK can start`() {
+ with(testRule) {
+ assertFalse(embrace.isStarted)
+ embrace.start(harness.fakeCoreModule.context)
+ assertEquals(AppFramework.NATIVE, harness.appFramework)
+ assertFalse(harness.essentialServiceModule.configService.isSdkDisabled())
+ assertTrue(embrace.isStarted)
+ }
+ }
+
+ @Test
+ fun `SDK start defaults to native app framework`() {
+ with(testRule) {
+ assertFalse(embrace.isStarted)
+ embrace.start(harness.fakeCoreModule.context, false)
+ assertEquals(AppFramework.NATIVE, harness.appFramework)
+ assertTrue(embrace.isStarted)
+ }
+ }
+
+ @Test
+ fun `SDK disabled via the binary cannot start`() {
+ with(testRule) {
+ ApkToolsConfig.IS_SDK_DISABLED = true
+ embrace.start(harness.fakeCoreModule.context)
+ assertFalse(embrace.isStarted)
+ }
+ }
+
+ @Test
+ fun `SDK disabled via config cannot start`() {
+ with(testRule) {
+ harness.fakeConfigService.sdkDisabled = true
+ embrace.start(harness.fakeCoreModule.context)
+ assertFalse(embrace.isStarted)
+ }
+ }
+
+ @Test
+ fun `custom appId must be valid`() {
+ with(testRule) {
+ assertFalse(embrace.setAppId(""))
+ assertFalse(embrace.setAppId("abcd"))
+ assertFalse(embrace.setAppId("abcdef"))
+ assertTrue(embrace.setAppId("abcde"))
+ }
+ }
+
+ @Test
+ fun `custom appId cannot be set after start`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ assertTrue(embrace.isStarted)
+ assertFalse(embrace.setAppId("xyz12"))
+ }
+ }
+
+ @Test
+ fun `getCurrentSessionId returns null when SDK is not started`() {
+ with(testRule) {
+ assertNull(embrace.currentSessionId)
+ }
+ }
+
+ @Test
+ fun `getCurrentSessionId returns sessionId when SDK is started and foreground session is active`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ harness.recordSession {
+ assertEquals(embrace.currentSessionId, harness.essentialServiceModule.metadataService.activeSessionId)
+ assertNotNull(embrace.currentSessionId)
+ }
+ }
+ }
+
+ @Test
+ fun `getCurrentSessionId returns sessionId when SDK is started and background session is active`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ var foregroundSessionId: String? = null
+ harness.recordSession {
+ foregroundSessionId = embrace.currentSessionId
+ }
+ val backgroundSessionId = embrace.currentSessionId
+ assertNotNull(backgroundSessionId)
+ assertNotEquals(foregroundSessionId, backgroundSessionId)
+ }
+ }
+
+ @Test
+ fun `ensure all generated W3C traceparent conforms to the expected format`() {
+ with(testRule) {
+ repeat(100) {
+ assertTrue(validPattern.matches(embrace.generateW3cTraceparent()))
+ }
+ }
+ }
+}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt
new file mode 100644
index 0000000000..a1a4ecd03f
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt
@@ -0,0 +1,195 @@
+package io.embrace.android.embracesdk.testcases
+
+import android.os.Build.VERSION_CODES.TIRAMISU
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData
+import io.embrace.android.embracesdk.comms.delivery.SessionMessageState
+import io.embrace.android.embracesdk.fixtures.TOO_LONG_ATTRIBUTE_KEY
+import io.embrace.android.embracesdk.fixtures.TOO_LONG_ATTRIBUTE_VALUE
+import io.embrace.android.embracesdk.recordSession
+import io.embrace.android.embracesdk.returnIfConditionMet
+import io.embrace.android.embracesdk.spans.EmbraceSpanEvent
+import io.embrace.android.embracesdk.spans.ErrorCode
+import io.opentelemetry.api.trace.SpanId
+import io.opentelemetry.api.trace.StatusCode
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import java.util.concurrent.TimeUnit
+
+@Config(sdk = [TIRAMISU])
+@RunWith(AndroidJUnit4::class)
+internal class TracingApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule(
+ harnessSupplier = {
+ IntegrationTestRule.newHarness(startImmediately = false)
+ }
+ )
+
+ @Test
+ fun `check spans logged in the right session when service is initialized after a session starts`() {
+ val testStartTime = testRule.harness.fakeClock.now()
+ with(testRule) {
+ harness.fakeClock.tick(100L)
+ embrace.start(harness.fakeCoreModule.context)
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ assertTrue(
+ returnIfConditionMet(desiredValueSupplier = { true }, waitTimeMs = 1000) { embrace.isTracingAvailable() }
+ )
+ val parentSpan = checkNotNull(embrace.createSpan(name = "test-trace-root"))
+ assertTrue(parentSpan.start())
+ assertTrue(parentSpan.addAttribute("oMg", "OmG"))
+ assertTrue(embrace.recordSpan(name = "record-span-span", parent = parentSpan) {
+ harness.fakeClock.tick(100L)
+ parentSpan.addEvent("parent event")
+ true
+ })
+ val failedOpStartTime = harness.fakeClock.now()
+ harness.fakeClock.tick(200L)
+ parentSpan.addEvent(name = "delayed event", time = harness.fakeClock.now() - 50L, null)
+ val failedOpEndTime = harness.fakeClock.now()
+
+ assertTrue(parentSpan.stop())
+
+ val attributes = mapOf(
+ Pair(TOO_LONG_ATTRIBUTE_KEY, "value"),
+ Pair("test-attr", "false"),
+ Pair("myKey", TOO_LONG_ATTRIBUTE_VALUE)
+ )
+
+ val events = listOf(
+ checkNotNull(
+ EmbraceSpanEvent.create(
+ name = "failure time",
+ timestampNanos = failedOpEndTime,
+ attributes = mapOf(
+ Pair("retry", "1")
+ )
+ )
+ )
+ )
+ assertTrue(
+ embrace.recordCompletedSpan(
+ name = "completed-span",
+ startTimeNanos = TimeUnit.MILLISECONDS.toNanos(failedOpStartTime),
+ endTimeNanos = TimeUnit.MILLISECONDS.toNanos(failedOpEndTime),
+ errorCode = ErrorCode.FAILURE,
+ parent = parentSpan,
+ attributes = attributes,
+ events = events
+ )
+ )
+ harness.fakeClock.tick(300L)
+ embrace.endAppStartup()
+ assertTrue(
+ returnIfConditionMet(desiredValueSupplier = { true }, waitTimeMs = 1000) {
+ checkNotNull(harness.initModule.spansService.completedSpans()).size == 5
+ }
+ )
+ }
+ val sessionEndTime = harness.fakeClock.now()
+ assertEquals(2, harness.fakeDeliveryModule.deliveryService.lastSentSessions.size)
+ val startSession = harness.fakeDeliveryModule.deliveryService.lastSentSessions[0]
+ assertEquals(SessionMessageState.START, startSession.second)
+ val endSession = harness.fakeDeliveryModule.deliveryService.lastSentSessions[1]
+ assertEquals(SessionMessageState.END, endSession.second)
+ with(endSession.first) {
+ checkNotNull(spans)
+ assertEquals(6, spans.size)
+ val spansMap = spans.associateBy { it.name }
+ val sessionSpan = checkNotNull(spansMap["emb-session-span"])
+ val traceRootSpan = checkNotNull(spansMap["test-trace-root"])
+ assertEmbraceSpanData(
+ span = spansMap["emb-sdk-init"],
+ expectedStartTimeMs = testStartTime + 100,
+ expectedEndTimeMs = testStartTime + 100,
+ expectedParentId = SpanId.getInvalid(),
+ expectedEvents = listOf(
+ checkNotNull(
+ EmbraceSpanEvent.create(
+ name = "start-time",
+ timestampNanos = TimeUnit.MILLISECONDS.toNanos(testStartTime + 100),
+ attributes = null
+ )
+ )
+ ),
+ private = true,
+ key = true
+ )
+ assertEmbraceSpanData(
+ span = spansMap["emb-startup-moment"],
+ expectedStartTimeMs = testStartTime + 100,
+ expectedEndTimeMs = testStartTime + 700,
+ expectedParentId = SpanId.getInvalid(),
+ key = true
+ )
+ assertEmbraceSpanData(
+ span = traceRootSpan,
+ expectedStartTimeMs = testStartTime + 100,
+ expectedEndTimeMs = testStartTime + 400,
+ expectedParentId = SpanId.getInvalid(),
+ expectedCustomAttributes = mapOf(Pair("oMg", "OmG")),
+ expectedEvents = listOf(
+ checkNotNull(
+ EmbraceSpanEvent.create(
+ name = "parent event",
+ timestampNanos = TimeUnit.MILLISECONDS.toNanos(testStartTime + 200),
+ attributes = null
+ )
+ ),
+ checkNotNull(
+ EmbraceSpanEvent.create(
+ name = "delayed event",
+ timestampNanos = TimeUnit.MILLISECONDS.toNanos(testStartTime + 350),
+ attributes = null
+ ),
+ )
+ ),
+ key = true
+ )
+ assertEmbraceSpanData(
+ span = spansMap["record-span-span"],
+ expectedStartTimeMs = testStartTime + 100,
+ expectedEndTimeMs = testStartTime + 200,
+ expectedParentId = traceRootSpan.spanId,
+ expectedTraceId = traceRootSpan.traceId
+ )
+
+ assertEmbraceSpanData(
+ span = spansMap["completed-span"],
+ expectedStartTimeMs = testStartTime + 200,
+ expectedEndTimeMs = testStartTime + 400,
+ expectedParentId = traceRootSpan.spanId,
+ expectedTraceId = traceRootSpan.traceId,
+ expectedStatus = StatusCode.ERROR,
+ expectedCustomAttributes = mapOf(Pair("test-attr", "false")),
+ expectedEvents = listOf(
+ checkNotNull(
+ EmbraceSpanEvent.create(
+ name = "failure time",
+ timestampNanos = testStartTime + 400,
+ attributes = mapOf(
+ Pair("retry", "1")
+ )
+ )
+ )
+ )
+ )
+ assertEmbraceSpanData(
+ span = sessionSpan,
+ expectedStartTimeMs = testStartTime + 100,
+ expectedEndTimeMs = sessionEndTime,
+ expectedParentId = SpanId.getInvalid(),
+ private = true
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/main/AndroidManifest.xml b/embrace-android-sdk/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..6a4872c3c1
--- /dev/null
+++ b/embrace-android-sdk/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embrace-android-sdk/src/main/baseline-prof.txt b/embrace-android-sdk/src/main/baseline-prof.txt
new file mode 100644
index 0000000000..6ff64ccdf6
--- /dev/null
+++ b/embrace-android-sdk/src/main/baseline-prof.txt
@@ -0,0 +1,2835 @@
+HSPLio/embrace/android/embracesdk/AutomaticVerificationExceptionHandler;->(Ljava/lang/Thread$UncaughtExceptionHandler;)V
+HSPLio/embrace/android/embracesdk/BuildInfo$Companion;->()V
+HSPLio/embrace/android/embracesdk/BuildInfo$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/BuildInfo$Companion;->fromResources(Lio/embrace/android/embracesdk/internal/AndroidResourcesService;Ljava/lang/String;)Lio/embrace/android/embracesdk/BuildInfo;
+HSPLio/embrace/android/embracesdk/BuildInfo$Companion;->getBuildResource(Lio/embrace/android/embracesdk/internal/AndroidResourcesService;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/BuildInfo;->()V
+HSPLio/embrace/android/embracesdk/BuildInfo;->(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/BuildInfo;->fromResources(Lio/embrace/android/embracesdk/internal/AndroidResourcesService;Ljava/lang/String;)Lio/embrace/android/embracesdk/BuildInfo;
+HSPLio/embrace/android/embracesdk/BuildInfo;->getBuildFlavor()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/BuildInfo;->getBuildId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/BuildInfo;->getBuildType()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/Embrace$AppFramework;->()V
+HSPLio/embrace/android/embracesdk/Embrace$AppFramework;->(Ljava/lang/String;II)V
+HSPLio/embrace/android/embracesdk/Embrace$AppFramework;->getValue()I
+HSPLio/embrace/android/embracesdk/Embrace$AppFramework;->values()[Lio/embrace/android/embracesdk/Embrace$AppFramework;
+HSPLio/embrace/android/embracesdk/Embrace;->()V
+HSPLio/embrace/android/embracesdk/Embrace;->()V
+HSPLio/embrace/android/embracesdk/Embrace;->enableDebugLogging()V
+HSPLio/embrace/android/embracesdk/Embrace;->generateW3cTraceparent()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/Embrace;->getConfigService()Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/Embrace;->getInstance()Lio/embrace/android/embracesdk/Embrace;
+HSPLio/embrace/android/embracesdk/Embrace;->getTraceIdHeader()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/Embrace;->isStarted()Z
+HSPLio/embrace/android/embracesdk/Embrace;->logInfo(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/Embrace;->logMessage(Ljava/lang/String;Lio/embrace/android/embracesdk/Severity;)V
+HSPLio/embrace/android/embracesdk/Embrace;->logMessage(Ljava/lang/String;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;)V
+HSPLio/embrace/android/embracesdk/Embrace;->recordNetworkRequest(Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;)V
+HSPLio/embrace/android/embracesdk/Embrace;->shouldCaptureNetworkBody(Ljava/lang/String;Ljava/lang/String;)Z
+HSPLio/embrace/android/embracesdk/Embrace;->start(Landroid/content/Context;Z)V
+HSPLio/embrace/android/embracesdk/Embrace;->start(Landroid/content/Context;ZLio/embrace/android/embracesdk/Embrace$AppFramework;)V
+HSPLio/embrace/android/embracesdk/Embrace;->verifyNonNullParameters(Ljava/lang/String;[Ljava/lang/Object;)Z
+HSPLio/embrace/android/embracesdk/EmbraceCpuInfoDelegate;->(Lio/embrace/android/embracesdk/internal/SharedObjectLoader;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type$Companion$WhenMappings;->()V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type$Companion;->()V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type$Companion;->fromSeverity(Lio/embrace/android/embracesdk/Severity;)Lio/embrace/android/embracesdk/EmbraceEvent$Type;
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type;->()V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type;->(Ljava/lang/String;ILjava/lang/String;)V
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type;->getAbbreviation()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/EmbraceEvent$Type;->values()[Lio/embrace/android/embracesdk/EmbraceEvent$Type;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda0;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda0;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda10;->(Lio/embrace/android/embracesdk/EmbraceImpl;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda11;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda1;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda2;->(Lio/embrace/android/embracesdk/EmbraceImpl;JJ)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda2;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda3;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda3;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda4;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda4;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda5;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda5;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda6;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda6;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda7;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda7;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda8;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda8;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda9;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl$$ExternalSyntheticLambda9;->invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->(Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function11;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function5;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->generateW3cTraceparent()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->getConfigService()Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->getTraceIdHeader()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->isStarted()Z
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->lambda$startImpl$2()Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->lambda$startImpl$3$io-embrace-android-embracesdk-EmbraceImpl(JJ)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->loadCrashVerifier(Lio/embrace/android/embracesdk/injection/CrashModule;Lio/embrace/android/embracesdk/worker/WorkerThreadModule;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->logMessage(Lio/embrace/android/embracesdk/EmbraceEvent$Type;Ljava/lang/String;Ljava/util/Map;Z[Ljava/lang/StackTraceElement;Ljava/lang/String;Lio/embrace/android/embracesdk/LogExceptionType;Ljava/lang/String;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->logMessage(Lio/embrace/android/embracesdk/EmbraceEvent$Type;Ljava/lang/String;Ljava/util/Map;Z[Ljava/lang/StackTraceElement;Ljava/lang/String;Lio/embrace/android/embracesdk/LogExceptionType;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->logMessage(Ljava/lang/String;Lio/embrace/android/embracesdk/Severity;Ljava/util/Map;Z)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->logNetworkRequestImpl(Lio/embrace/android/embracesdk/network/http/NetworkCaptureData;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Integer;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->normalizeProperties(Ljava/util/Map;)Ljava/util/Map;
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->onActivityReported()V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->recordNetworkRequest(Lio/embrace/android/embracesdk/network/EmbraceNetworkRequest;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->shouldCaptureNetworkCall(Ljava/lang/String;Ljava/lang/String;)Z
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->start(Landroid/content/Context;ZLio/embrace/android/embracesdk/Embrace$AppFramework;)V
+HSPLio/embrace/android/embracesdk/EmbraceImpl;->startImpl(Landroid/content/Context;ZLio/embrace/android/embracesdk/Embrace$AppFramework;)V
+HSPLio/embrace/android/embracesdk/EmbraceInternalInterfaceImpl;->(Lio/embrace/android/embracesdk/EmbraceImpl;)V
+HSPLio/embrace/android/embracesdk/EmbraceLogger$Severity;->()V
+HSPLio/embrace/android/embracesdk/EmbraceLogger$Severity;->(Ljava/lang/String;I)V
+HSPLio/embrace/android/embracesdk/EmbraceLogger$Severity;->values()[Lio/embrace/android/embracesdk/EmbraceLogger$Severity;
+HSPLio/embrace/android/embracesdk/FlutterInternalInterfaceImpl;->(Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/EmbraceInternalInterface;Lio/embrace/android/embracesdk/capture/metadata/MetadataService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$embraceInternalInterface$2;->(Lio/embrace/android/embracesdk/EmbraceImpl;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$embraceInternalInterface$2;->invoke()Lio/embrace/android/embracesdk/EmbraceInternalInterfaceImpl;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$embraceInternalInterface$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$flutterInternalInterface$2;->(Lio/embrace/android/embracesdk/InternalInterfaceModuleImpl;Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/injection/CoreModule;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$flutterInternalInterface$2;->invoke()Lio/embrace/android/embracesdk/FlutterInternalInterfaceImpl;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$flutterInternalInterface$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$reactNativeInternalInterface$2;->(Lio/embrace/android/embracesdk/InternalInterfaceModuleImpl;Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/injection/CoreModule;Lio/embrace/android/embracesdk/injection/AndroidServicesModule;Lio/embrace/android/embracesdk/injection/CrashModule;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$reactNativeInternalInterface$2;->invoke()Lio/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$reactNativeInternalInterface$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$unityInternalInterface$2;->(Lio/embrace/android/embracesdk/InternalInterfaceModuleImpl;Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/injection/AndroidServicesModule;Lio/embrace/android/embracesdk/injection/CoreModule;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$unityInternalInterface$2;->invoke()Lio/embrace/android/embracesdk/UnityInternalInterfaceImpl;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl$unityInternalInterface$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->()V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->(Lio/embrace/android/embracesdk/injection/CoreModule;Lio/embrace/android/embracesdk/injection/AndroidServicesModule;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/injection/CrashModule;)V
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->getEmbraceInternalInterface()Lio/embrace/android/embracesdk/EmbraceInternalInterface;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->getFlutterInternalInterface()Lio/embrace/android/embracesdk/FlutterInternalInterface;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->getReactNativeInternalInterface()Lio/embrace/android/embracesdk/ReactNativeInternalInterface;
+HSPLio/embrace/android/embracesdk/InternalInterfaceModuleImpl;->getUnityInternalInterface()Lio/embrace/android/embracesdk/UnityInternalInterface;
+HSPLio/embrace/android/embracesdk/LogExceptionType;->()V
+HSPLio/embrace/android/embracesdk/LogExceptionType;->(Ljava/lang/String;ILjava/lang/String;)V
+HSPLio/embrace/android/embracesdk/LogExceptionType;->getValue$embrace_android_sdk_release()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl;->(Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/EmbraceInternalInterface;Lio/embrace/android/embracesdk/Embrace$AppFramework;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/capture/crash/CrashService;Lio/embrace/android/embracesdk/capture/metadata/MetadataService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$backgroundActivityService$2;->(Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/injection/DataContainerModule;Lio/embrace/android/embracesdk/injection/DataCaptureServiceModule;Lio/embrace/android/embracesdk/injection/CustomerLogModule;Lio/embrace/android/embracesdk/injection/SdkObservabilityModule;Lio/embrace/android/embracesdk/injection/DeliveryModule;Lio/embrace/android/embracesdk/ndk/NativeModule;Lio/embrace/android/embracesdk/injection/InitModule;Lio/embrace/android/embracesdk/worker/WorkerThreadModule;)V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$backgroundActivityService$2;->invoke()Lio/embrace/android/embracesdk/EmbraceBackgroundActivityService;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$backgroundActivityService$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionHandler$2;->(Lio/embrace/android/embracesdk/injection/CoreModule;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/injection/AndroidServicesModule;Lio/embrace/android/embracesdk/injection/DataCaptureServiceModule;Lio/embrace/android/embracesdk/ndk/NativeModule;Lio/embrace/android/embracesdk/injection/DataContainerModule;Lio/embrace/android/embracesdk/injection/CustomerLogModule;Lio/embrace/android/embracesdk/injection/SdkObservabilityModule;Lio/embrace/android/embracesdk/injection/DeliveryModule;Lio/embrace/android/embracesdk/injection/InitModule;Lio/embrace/android/embracesdk/worker/WorkerThreadModule;)V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionHandler$2;->invoke()Lio/embrace/android/embracesdk/session/SessionHandler;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionHandler$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionService$2;->(Lio/embrace/android/embracesdk/SessionModuleImpl;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/ndk/NativeModule;Lio/embrace/android/embracesdk/session/EmbraceSessionProperties;Lio/embrace/android/embracesdk/injection/CoreModule;Lio/embrace/android/embracesdk/injection/DeliveryModule;Lio/embrace/android/embracesdk/injection/InitModule;)V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionService$2;->invoke()Lio/embrace/android/embracesdk/session/EmbraceSessionService;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl$sessionService$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl;->()V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl;->(Lio/embrace/android/embracesdk/injection/InitModule;Lio/embrace/android/embracesdk/injection/CoreModule;Lio/embrace/android/embracesdk/injection/AndroidServicesModule;Lio/embrace/android/embracesdk/injection/EssentialServiceModule;Lio/embrace/android/embracesdk/ndk/NativeModule;Lio/embrace/android/embracesdk/injection/DataContainerModule;Lio/embrace/android/embracesdk/injection/DeliveryModule;Lio/embrace/android/embracesdk/session/EmbraceSessionProperties;Lio/embrace/android/embracesdk/injection/DataCaptureServiceModule;Lio/embrace/android/embracesdk/injection/CustomerLogModule;Lio/embrace/android/embracesdk/injection/SdkObservabilityModule;Lio/embrace/android/embracesdk/worker/WorkerThreadModule;)V
+HSPLio/embrace/android/embracesdk/SessionModuleImpl;->getBackgroundActivityService()Lio/embrace/android/embracesdk/session/BackgroundActivityService;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl;->getSessionHandler()Lio/embrace/android/embracesdk/session/SessionHandler;
+HSPLio/embrace/android/embracesdk/SessionModuleImpl;->getSessionService()Lio/embrace/android/embracesdk/session/SessionService;
+HSPLio/embrace/android/embracesdk/Severity;->()V
+HSPLio/embrace/android/embracesdk/Severity;->(Ljava/lang/String;I)V
+HSPLio/embrace/android/embracesdk/Severity;->values()[Lio/embrace/android/embracesdk/Severity;
+HSPLio/embrace/android/embracesdk/UnityInternalInterfaceImpl;->(Lio/embrace/android/embracesdk/EmbraceImpl;Lio/embrace/android/embracesdk/EmbraceInternalInterface;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler$Companion;->()V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->()V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/clock/Clock;Ljava/lang/Thread;Ljava/util/concurrent/atomic/AtomicReference;Ljava/util/concurrent/ExecutorService;)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->findIntervalsWithSamples()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->getAnrIntervals(Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Lio/embrace/android/embracesdk/clock/Clock;)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->onThreadBlocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->onThreadBlockedInterval(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->onThreadUnblocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->reachedAnrStacktraceCaptureLimit$embrace_android_sdk_release()Z
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->setConfigService(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/AnrStacktraceSampler;->size$embrace_android_sdk_release()I
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$Companion;->()V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$getCapturedData$callable$1;->(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$getCapturedData$callable$1;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$getCapturedData$callable$1;->call()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$onForeground$1;->(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$onForeground$1;->run()V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$startAnrCapture$1;->(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService$startAnrCapture$1;->run()V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->()V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->(Lio/embrace/android/embracesdk/config/ConfigService;Landroid/os/Looper;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Lio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;Lio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;Ljava/util/concurrent/ScheduledExecutorService;Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Lio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;Lio/embrace/android/embracesdk/clock/Clock;Ljava/util/concurrent/atomic/AtomicReference;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->access$getAnrMonitorThread$p(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)Ljava/util/concurrent/atomic/AtomicReference;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->access$getState$p(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->access$getTargetThreadHeartbeatScheduler$p(Lio/embrace/android/embracesdk/anr/EmbraceAnrService;)Lio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->finishInitialization(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->getAnrProcessErrors(J)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->getClock()Lio/embrace/android/embracesdk/clock/Clock;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->getStacktraceSampler()Lio/embrace/android/embracesdk/anr/AnrStacktraceSampler;
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onThreadBlocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onThreadBlockedInterval(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onThreadUnblocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->processAnrTick$embrace_android_sdk_release(J)V
+HSPLio/embrace/android/embracesdk/anr/EmbraceAnrService;->startAnrCapture()V
+HSPLio/embrace/android/embracesdk/anr/ThreadInfoCollector;->(Ljava/lang/Thread;)V
+HSPLio/embrace/android/embracesdk/anr/ThreadInfoCollector;->captureSample(Lio/embrace/android/embracesdk/config/ConfigService;)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/ThreadInfoCollector;->clearStacktraceCache()V
+HSPLio/embrace/android/embracesdk/anr/ThreadInfoCollector;->getAllowedThreads$embrace_android_sdk_release(Lio/embrace/android/embracesdk/config/ConfigService;)Ljava/util/Set;
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->(Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/config/ConfigService;Ljava/util/concurrent/ScheduledExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;I)V
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->(Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/config/ConfigService;Ljava/util/concurrent/ScheduledExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->getAnrProcessErrors(J)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->isFeatureEnabled()Z
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->onThreadBlocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->onThreadBlockedInterval(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/AnrProcessErrorSampler;->onThreadUnblocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/anr/BlockedThreadListener;Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Ljava/lang/Thread;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Ljava/util/concurrent/atomic/AtomicReference;)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/anr/BlockedThreadListener;Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Ljava/lang/Thread;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Ljava/util/concurrent/atomic/AtomicReference;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->getConfigService()Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->isAnrDurationThresholdExceeded$embrace_android_sdk_release(J)Z
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->isDebuggerEnabled()Z
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->onTargetThreadResponse(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->setConfigService(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->setListener(Lio/embrace/android/embracesdk/anr/BlockedThreadListener;)V
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->shouldAttemptAnrSample$embrace_android_sdk_release(J)Z
+HSPLio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;->updateAnrTracking(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$1;->(Lio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$1;->invoke(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$1;->invoke(Ljava/lang/Object;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$sam$java_lang_Runnable$0;->(Lkotlin/jvm/functions/Function0;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$sam$java_lang_Runnable$0;->run()V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$scheduleRegularHeartbeats$runnable$1;->(Lio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$scheduleRegularHeartbeats$runnable$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler$scheduleRegularHeartbeats$runnable$1;->invoke()V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->(Lio/embrace/android/embracesdk/config/ConfigService;Ljava/util/concurrent/ScheduledExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Lio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;Lio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Ljava/util/concurrent/atomic/AtomicReference;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->(Lio/embrace/android/embracesdk/config/ConfigService;Ljava/util/concurrent/ScheduledExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;Lio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;Lio/embrace/android/embracesdk/anr/detection/BlockedThreadDetector;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Ljava/util/concurrent/atomic/AtomicReference;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->getConfigService()Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->onMonitorThreadHeartbeat$embrace_android_sdk_release()V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->scheduleRegularHeartbeats()V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->sendHeartbeatMessage()V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->setConfigService(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->setListener(Lio/embrace/android/embracesdk/anr/BlockedThreadListener;)V
+HSPLio/embrace/android/embracesdk/anr/detection/LivenessCheckScheduler;->startMonitoringThread()V
+HSPLio/embrace/android/embracesdk/anr/detection/LooperCompat;->getMessageQueue(Landroid/os/Looper;)Landroid/os/MessageQueue;
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler$Companion;->()V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler$onMainThreadUnblocked$1;->(Lio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler$onMainThreadUnblocked$1;->run()V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->()V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->(Landroid/os/Looper;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/atomic/AtomicReference;Lio/embrace/android/embracesdk/config/ConfigService;Landroid/os/MessageQueue;Lio/embrace/android/embracesdk/clock/Clock;)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->(Landroid/os/Looper;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/atomic/AtomicReference;Lio/embrace/android/embracesdk/config/ConfigService;Landroid/os/MessageQueue;Lio/embrace/android/embracesdk/clock/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->access$getAnrMonitorThread$p(Lio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;)Ljava/util/concurrent/atomic/AtomicReference;
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->getAction()Lkotlin/jvm/functions/Function1;
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->handleMessage(Landroid/os/Message;)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->onMainThreadUnblocked()V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->setAction(Lkotlin/jvm/functions/Function1;)V
+HSPLio/embrace/android/embracesdk/anr/detection/TargetThreadHandler;->start()V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->(Lio/embrace/android/embracesdk/clock/Clock;)V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->getAnrInProgress()Z
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->getLastMonitorThreadResponseMs()J
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->getLastTargetThreadResponseMs()J
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->resetState()V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->setAnrInProgress(Z)V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->setLastMonitorThreadResponseMs(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->setLastSampleAttemptMs(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/ThreadMonitoringState;->setLastTargetThreadResponseMs(J)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->(Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->checkTimeTravel(Ljava/lang/String;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->checkUnbalancedCall(Ljava/lang/String;Z)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->onThreadBlocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->onThreadBlockedInterval(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/detection/UnbalancedCallDetector;->onThreadUnblocked(Ljava/lang/Thread;J)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/FilesDelegate;->()V
+HSPLio/embrace/android/embracesdk/anr/sigquit/FilesDelegate;->getCommandFileForThread(Ljava/lang/String;)Ljava/io/File;
+HSPLio/embrace/android/embracesdk/anr/sigquit/FilesDelegate;->getThreadsFileForCurrentProcess()Ljava/io/File;
+HSPLio/embrace/android/embracesdk/anr/sigquit/FindGoogleThread;->(Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Lio/embrace/android/embracesdk/anr/sigquit/GetThreadsInCurrentProcess;Lio/embrace/android/embracesdk/anr/sigquit/GetThreadCommand;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/FindGoogleThread;->invoke()I
+HSPLio/embrace/android/embracesdk/anr/sigquit/GetThreadCommand;->(Lio/embrace/android/embracesdk/anr/sigquit/FilesDelegate;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/GetThreadCommand;->invoke(Ljava/lang/String;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/anr/sigquit/GetThreadsInCurrentProcess;->(Lio/embrace/android/embracesdk/anr/sigquit/FilesDelegate;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/GetThreadsInCurrentProcess;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/sigquit/GoogleAnrHandlerNativeDelegate;->(Lio/embrace/android/embracesdk/anr/sigquit/GoogleAnrTimestampRepository;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/GoogleAnrHandlerNativeDelegate;->install(I)I
+HSPLio/embrace/android/embracesdk/anr/sigquit/GoogleAnrTimestampRepository;->(Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/GoogleAnrTimestampRepository;->getGoogleAnrTimestamps(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService$initializeGoogleAnrTracking$1;->(Lio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService$initializeGoogleAnrTracking$1;->onConfigChange(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService$setupGoogleAnrTracking$1;->(Lio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService$setupGoogleAnrTracking$1;->run()V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->(Lio/embrace/android/embracesdk/internal/SharedObjectLoader;Lio/embrace/android/embracesdk/anr/sigquit/FindGoogleThread;Lio/embrace/android/embracesdk/anr/sigquit/GoogleAnrHandlerNativeDelegate;Lio/embrace/android/embracesdk/anr/sigquit/GoogleAnrTimestampRepository;Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->access$setupGoogleAnrTracking(Lio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->initializeGoogleAnrTracking()V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->installGoogleAnrHandler(I)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->setConfigService(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->setupGoogleAnrHandler()V
+HSPLio/embrace/android/embracesdk/anr/sigquit/SigquitDetectionService;->setupGoogleAnrTracking()V
+HSPLio/embrace/android/embracesdk/capture/EmbracePerformanceInfoService;->(Lio/embrace/android/embracesdk/anr/AnrService;Lio/embrace/android/embracesdk/capture/connectivity/NetworkConnectivityService;Lio/embrace/android/embracesdk/network/logging/NetworkLoggingService;Lio/embrace/android/embracesdk/capture/powersave/PowerSaveModeService;Lio/embrace/android/embracesdk/capture/memory/MemoryService;Lio/embrace/android/embracesdk/capture/metadata/MetadataService;Lio/embrace/android/embracesdk/anr/sigquit/GoogleAnrTimestampRepository;Lio/embrace/android/embracesdk/capture/aei/ApplicationExitInfoService;Lio/embrace/android/embracesdk/capture/strictmode/StrictModeService;Lio/embrace/android/embracesdk/anr/ndk/NativeThreadSamplerService;)V
+HSPLio/embrace/android/embracesdk/capture/EmbracePerformanceInfoService;->getPerformanceInfo(JJZ)Lio/embrace/android/embracesdk/payload/PerformanceInfo;
+HSPLio/embrace/android/embracesdk/capture/EmbracePerformanceInfoService;->getSessionPerformanceInfo(JJZLjava/lang/Boolean;)Lio/embrace/android/embracesdk/payload/PerformanceInfo;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService$startService$1;->(Lio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService$startService$1;->run()V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->()V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->(Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/config/ConfigService;Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/comms/delivery/DeliveryService;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->access$processApplicationExitInfo(Lio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->buildSessionAppExitInfoData(Landroid/app/ApplicationExitInfo;Ljava/lang/String;Ljava/lang/String;)Lio/embrace/android/embracesdk/payload/AppExitInfoData;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->collectExitInfoTrace(Landroid/app/ApplicationExitInfo;)Lio/embrace/android/embracesdk/config/behavior/AppExitInfoBehavior$CollectTracesResult;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->generateUniqueHash(Landroid/app/ApplicationExitInfo;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->getHistoricalProcessExitReasons()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->getSessionIdValidationError(Ljava/lang/String;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->getUnsentExitReasons(Ljava/util/List;)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->onConfigChange(Lio/embrace/android/embracesdk/config/ConfigService;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->processApplicationExitInfo()V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->processApplicationExitInfoBlobs(Ljava/util/List;)V
+HSPLio/embrace/android/embracesdk/capture/aei/EmbraceApplicationExitInfoService;->startService()V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService$ipAddress$2;->(Lio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService$registerConnectivityActionReceiver$1;->(Lio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService$registerConnectivityActionReceiver$1;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->(Landroid/content/Context;Lio/embrace/android/embracesdk/clock/Clock;Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Landroid/net/ConnectivityManager;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->access$getContext$p(Lio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;)Landroid/content/Context;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->access$getIntentFilter$p(Lio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;)Landroid/content/IntentFilter;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->addNetworkConnectivityListener(Lio/embrace/android/embracesdk/capture/connectivity/NetworkConnectivityListener;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->getCurrentNetworkStatus()Lio/embrace/android/embracesdk/comms/delivery/NetworkStatus;
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->handleNetworkStatus$default(Lio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;ZJILjava/lang/Object;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->handleNetworkStatus(ZJ)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->networkStatusOnSessionStarted(J)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->registerConnectivityActionReceiver()V
+HSPLio/embrace/android/embracesdk/capture/connectivity/EmbraceNetworkConnectivityService;->saveStatus(JLio/embrace/android/embracesdk/comms/delivery/NetworkStatus;)Z
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceCrashService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceCrashService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceCrashService;->()V
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceCrashService;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/session/SessionService;Lio/embrace/android/embracesdk/capture/metadata/MetadataService;Lio/embrace/android/embracesdk/comms/delivery/DeliveryService;Lio/embrace/android/embracesdk/capture/user/UserService;Lio/embrace/android/embracesdk/event/EventService;Lio/embrace/android/embracesdk/anr/AnrService;Lio/embrace/android/embracesdk/ndk/NdkService;Lio/embrace/android/embracesdk/gating/GatingService;Lio/embrace/android/embracesdk/session/BackgroundActivityService;Lio/embrace/android/embracesdk/internal/crash/CrashFileMarker;Lio/embrace/android/embracesdk/clock/Clock;)V
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceCrashService;->registerExceptionHandler()V
+HSPLio/embrace/android/embracesdk/capture/crash/EmbraceUncaughtExceptionHandler;->(Ljava/lang/Thread$UncaughtExceptionHandler;Lio/embrace/android/embracesdk/capture/crash/CrashService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$2;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$3;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$3;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$4;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$4;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$5;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$5;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$6;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$6;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$7;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$7;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getCustomBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getCustomBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getCustomBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getFragmentBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getFragmentBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getFragmentBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getPushNotificationsBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getPushNotificationsBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getPushNotificationsBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getRnActionBreadcrumbForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getRnActionBreadcrumbForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getRnActionBreadcrumbForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getTapBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getTapBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getTapBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getViewBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getViewBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getViewBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getWebViewBreadcrumbsForSession$1;->(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;JJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getWebViewBreadcrumbsForSession$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService$getWebViewBreadcrumbsForSession$1;->invoke()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->(Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->access$filterBreadcrumbsForTimeWindow(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;Ljava/util/Deque;JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->access$getRnActionBreadcrumbs$p(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->access$getTapBreadcrumbs$p(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->access$getViewBreadcrumbs$p(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;)Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->access$isCacheValid(Lio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;Ljava/util/Deque;)I
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->addToViewLogsQueue(Ljava/lang/String;JZ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->filterBreadcrumbsForTimeWindow(Ljava/util/Deque;JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getBreadcrumbs(JJ)Lio/embrace/android/embracesdk/payload/Breadcrumbs;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getCustomBreadcrumbs()Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getCustomBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getFragmentBreadcrumbs()Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getFragmentBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getLastViewBreadcrumbScreenName()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getPushNotifications()Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getPushNotificationsBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getRnActionBreadcrumbForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getTapBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getViewBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getWebViewBreadcrumbs()Ljava/util/concurrent/LinkedBlockingDeque;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->getWebViewBreadcrumbsForSession(JJ)Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->isCacheValid(Ljava/util/Deque;)I
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->logView(Ljava/lang/String;J)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->replaceFirstSessionView(Ljava/lang/String;J)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/EmbraceBreadcrumbService;->tryAddBreadcrumb(Ljava/util/concurrent/LinkedBlockingDeque;Ljava/lang/Object;I)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService$Utils;->()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService$Utils;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->(Lio/embrace/android/embracesdk/capture/crumbs/BreadcrumbService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->isComingFromPushNotification(Landroid/app/Activity;)Z
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/capture/crumbs/PushNotificationCaptureService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService;->()V
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService;->(Lio/embrace/android/embracesdk/clock/Clock;)V
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/memory/EmbraceMemoryService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$1;->(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$3;->(Lio/embrace/android/embracesdk/BuildInfo;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$deviceIdentifier$1;->(Lio/embrace/android/embracesdk/prefs/PreferencesService;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$deviceIdentifier$1;->get()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isAppUpdated$1;->(Lio/embrace/android/embracesdk/prefs/PreferencesService;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isAppUpdated$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isAppUpdated$1;->invoke()Z
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isOsUpdated$1;->(Lio/embrace/android/embracesdk/prefs/PreferencesService;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isOsUpdated$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion$ofContext$isOsUpdated$1;->invoke()Z
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$Companion;->ofContext(Landroid/content/Context;Lio/embrace/android/embracesdk/BuildInfo;Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/Embrace$AppFramework;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/session/ActivityService;Ljava/util/concurrent/ExecutorService;Landroid/app/usage/StorageStatsManager;Landroid/view/WindowManager;Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/CpuInfoDelegate;Lio/embrace/android/embracesdk/internal/DeviceArchitecture;)Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveDiskUsage$1;->(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;Z)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveDiskUsage$1;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveIsJailbroken$1;->(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveIsJailbroken$1;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveScreenResolution$1;->(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$asyncRetrieveScreenResolution$1;->run()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$statFs$1;->()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$statFs$1;->()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$statFs$1;->invoke()Landroid/os/StatFs;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService$statFs$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->(Landroid/view/WindowManager;Landroid/content/pm/PackageManager;Landroid/app/usage/StorageStatsManager;Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/BuildInfo;Lio/embrace/android/embracesdk/config/ConfigService;Landroid/content/pm/ApplicationInfo;Lkotlin/Lazy;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/Embrace$AppFramework;Lkotlin/Lazy;Lkotlin/Lazy;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/session/ActivityService;Lkotlin/Lazy;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/CpuInfoDelegate;Lio/embrace/android/embracesdk/internal/DeviceArchitecture;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->(Landroid/view/WindowManager;Landroid/content/pm/PackageManager;Landroid/app/usage/StorageStatsManager;Landroid/app/ActivityManager;Lio/embrace/android/embracesdk/BuildInfo;Lio/embrace/android/embracesdk/config/ConfigService;Landroid/content/pm/ApplicationInfo;Lkotlin/Lazy;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/Embrace$AppFramework;Lkotlin/Lazy;Lkotlin/Lazy;Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/session/ActivityService;Lkotlin/Lazy;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/CpuInfoDelegate;Lio/embrace/android/embracesdk/internal/DeviceArchitecture;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getConfigService$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Lio/embrace/android/embracesdk/config/ConfigService;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getDiskUsage$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Lio/embrace/android/embracesdk/payload/DiskUsage;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getPackageManager$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Landroid/content/pm/PackageManager;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getPackageName$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getPreferencesService$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Lio/embrace/android/embracesdk/prefs/PreferencesService;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getScreenResolution$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getStatFs$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Lkotlin/Lazy;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getStorageStatsManager$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Landroid/app/usage/StorageStatsManager;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$getWindowManager$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Landroid/view/WindowManager;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$isJailbroken$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;)Ljava/lang/Boolean;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$setDiskUsage$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;Lio/embrace/android/embracesdk/payload/DiskUsage;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$setJailbroken$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;Ljava/lang/Boolean;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->access$setScreenResolution$p(Lio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->asyncRetrieveAdditionalDeviceInfo()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->asyncRetrieveDiskUsage(Z)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->asyncRetrieveIsJailbroken()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->asyncRetrieveScreenResolution()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getActiveSessionId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getAppId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getAppInfo()Lio/embrace/android/embracesdk/payload/AppInfo;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getAppInfo(Z)Lio/embrace/android/embracesdk/payload/AppInfo;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getAppState()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getAppVersionName()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getCpuName()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getDeviceId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getDeviceInfo()Lio/embrace/android/embracesdk/payload/DeviceInfo;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getDeviceInfo(Z)Lio/embrace/android/embracesdk/payload/DeviceInfo;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getDiskUsage()Lio/embrace/android/embracesdk/payload/DiskUsage;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getEgl()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->getScreenResolution()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->isJailbroken()Ljava/lang/Boolean;
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->precomputeValues()V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->setActiveSessionId(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService;->setSessionIdToProcessStateSummary(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->()V
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->appEnvironment(Landroid/content/pm/ApplicationInfo;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getDeviceDiskAppUsage(Landroid/app/usage/StorageStatsManager;Landroid/content/pm/PackageManager;Ljava/lang/String;)Ljava/lang/Long;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getDeviceManufacturer()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getInternalStorageFreeCapacity(Landroid/os/StatFs;)J
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getInternalStorageTotalCapacity(Landroid/os/StatFs;)J
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getLocale()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getModel()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getNumberOfCores()I
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getOperatingSystemType()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getOperatingSystemVersion()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getOperatingSystemVersionCode()I
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getScreenResolution(Landroid/view/WindowManager;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getSystemUptime()Ljava/lang/Long;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->getTimezoneId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->isEmulator()Z
+HSPLio/embrace/android/embracesdk/capture/metadata/MetadataUtils;->isJailbroken()Z
+HSPLio/embrace/android/embracesdk/capture/orientation/NoOpOrientationService;->()V
+HSPLio/embrace/android/embracesdk/capture/orientation/NoOpOrientationService;->onOrientationChanged(Ljava/lang/Integer;)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService$registerPowerSaveModeReceiver$1;->(Lio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService$registerPowerSaveModeReceiver$1;->run()V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->(Landroid/content/Context;Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/clock/Clock;Landroid/os/PowerManager;)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->access$getContext$p(Lio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;)Landroid/content/Context;
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->access$getPowerSaveIntentFilter$p(Lio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;)Landroid/content/IntentFilter;
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->access$getTag$p(Lio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/capture/powersave/EmbracePowerSaveModeService;->registerPowerSaveModeReceiver()V
+HSPLio/embrace/android/embracesdk/capture/screenshot/EmbraceScreenshotService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/screenshot/EmbraceScreenshotService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/screenshot/EmbraceScreenshotService;->()V
+HSPLio/embrace/android/embracesdk/capture/screenshot/EmbraceScreenshotService;->(Lio/embrace/android/embracesdk/session/ActivityService;Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/comms/delivery/DeliveryService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Lio/embrace/android/embracesdk/clock/Clock;)V
+HSPLio/embrace/android/embracesdk/capture/strictmode/NoOpStrictModeService;->()V
+HSPLio/embrace/android/embracesdk/capture/strictmode/NoOpStrictModeService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/strictmode/NoOpStrictModeService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/capture/strictmode/NoOpStrictModeService;->start()V
+HSPLio/embrace/android/embracesdk/capture/thermalstate/NoOpThermalStatusService;->()V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->()V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->(Lio/embrace/android/embracesdk/prefs/PreferencesService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->applicationStartupComplete()V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->getUserInfo()Lio/embrace/android/embracesdk/payload/UserInfo;
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->loadUserInfoFromDisk()Lio/embrace/android/embracesdk/payload/UserInfo;
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->onForeground(ZJJ)V
+HSPLio/embrace/android/embracesdk/capture/user/EmbraceUserService;->onView(Landroid/app/Activity;)V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService$Companion;->()V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService$webVitalType$1;->()V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService;->()V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/internal/EmbraceSerializer;)V
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService;->getCapturedData()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/capture/webview/EmbraceWebViewService;->getCapturedData()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/clock/NormalizedIntervalClock;->(Lio/embrace/android/embracesdk/clock/SystemClock;)V
+HSPLio/embrace/android/embracesdk/clock/NormalizedIntervalClock;->now()J
+HSPLio/embrace/android/embracesdk/clock/SystemClock;->()V
+HSPLio/embrace/android/embracesdk/clock/SystemClock;->now()J
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient$Companion;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->(Lio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;Lio/embrace/android/embracesdk/internal/EmbraceSerializer;Lkotlin/jvm/functions/Function2;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->executeHttpRequest(Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;)Lio/embrace/android/embracesdk/comms/api/ApiResponse;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->getCachedConfig()Lio/embrace/android/embracesdk/comms/api/CachedConfig;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->getConfig()Lio/embrace/android/embracesdk/config/remote/RemoteConfig;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->gzip([B)[B
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->handleRemoteConfigResponse(Lio/embrace/android/embracesdk/comms/api/ApiResponse;Lio/embrace/android/embracesdk/config/remote/RemoteConfig;)Lio/embrace/android/embracesdk/config/remote/RemoteConfig;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->post(Lio/embrace/android/embracesdk/comms/api/ApiRequest;[B)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->prepareConfigRequest(Ljava/lang/String;)Lio/embrace/android/embracesdk/comms/api/ApiRequest;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->rawPost(Lio/embrace/android/embracesdk/comms/api/ApiRequest;[B)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->readHttpResponseCode(Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;)I
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->readHttpResponseHeaders(Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;)Ljava/util/Map;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->readResponseBodyAsString(Ljava/io/InputStream;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiClient;->setTimeouts(Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;Lio/embrace/android/embracesdk/network/http/HttpMethod;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;Lio/embrace/android/embracesdk/network/http/HttpMethod;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->copy$default(Lio/embrace/android/embracesdk/comms/api/ApiRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;Lio/embrace/android/embracesdk/network/http/HttpMethod;Ljava/lang/String;ILjava/lang/Object;)Lio/embrace/android/embracesdk/comms/api/ApiRequest;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->copy(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;Lio/embrace/android/embracesdk/network/http/HttpMethod;Ljava/lang/String;)Lio/embrace/android/embracesdk/comms/api/ApiRequest;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->getHeaders()Ljava/util/Map;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->getHttpMethod()Lio/embrace/android/embracesdk/network/http/HttpMethod;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->getUrl()Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->toConnection()Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;
+HSPLio/embrace/android/embracesdk/comms/api/ApiRequest;->toString()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponse;->(Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/Object;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponse;->getBody()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponse;->getStatusCode()Ljava/lang/Integer;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache$Companion;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache$cacheDir$2;->(Lkotlin/jvm/functions/Function0;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache$cacheDir$2;->invoke()Ljava/io/File;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache$cacheDir$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->(Lio/embrace/android/embracesdk/internal/EmbraceSerializer;Lkotlin/jvm/functions/Function0;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->(Lio/embrace/android/embracesdk/internal/EmbraceSerializer;Lkotlin/jvm/functions/Function0;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->getCacheDir()Ljava/io/File;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->initializeIfNeeded()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->retrieveCacheResponse(Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/ApiRequest;)Ljava/net/CacheResponse;
+HSPLio/embrace/android/embracesdk/comms/api/ApiResponseCache;->retrieveCachedConfig(Ljava/lang/String;Lio/embrace/android/embracesdk/comms/api/ApiRequest;)Lio/embrace/android/embracesdk/comms/api/CachedConfig;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder$Companion;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->()V
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->(Lio/embrace/android/embracesdk/config/ConfigService;Lio/embrace/android/embracesdk/capture/metadata/MetadataService;ZZ)V
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->buildUrl(Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getAppId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getAppVersion()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getBaseUrls()Lio/embrace/android/embracesdk/config/behavior/SdkEndpointBehavior;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getConfigBaseUrl()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getConfigUrl()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getCoreBaseUrl()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getEmbraceUrlWithSuffix(Ljava/lang/String;)Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->getOperatingSystemCode()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/api/ApiUrlBuilder;->isDebugBuild()Z
+HSPLio/embrace/android/embracesdk/comms/api/CachedConfig;->(Lio/embrace/android/embracesdk/config/remote/RemoteConfig;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/api/CachedConfig;->getConfig()Lio/embrace/android/embracesdk/config/remote/RemoteConfig;
+HSPLio/embrace/android/embracesdk/comms/api/CachedConfig;->isValid()Z
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->(Ljava/net/HttpURLConnection;Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->connect()V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->getHeaderFields()Ljava/util/Map;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->getInputStream()Ljava/io/InputStream;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->getOutputStream()Ljava/io/OutputStream;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->getResponseCode()I
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->setConnectTimeout(Ljava/lang/Integer;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->setDoOutput(Ljava/lang/Boolean;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->setReadTimeout(Ljava/lang/Integer;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->setRequestMethod(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceConnectionImpl;->setRequestProperty(Ljava/lang/String;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl$Companion;->()V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl$Companion;->getUrl(Ljava/lang/String;)Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl;->()V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl;->()V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrl;->access$getEmbraceUrlFactory$cp()Lio/embrace/android/embracesdk/comms/api/EmbraceUrl$UrlFactory;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlAdapter;->()V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlAdapter;->read(Lcom/google/gson/stream/JsonReader;)Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlAdapter;->read(Lcom/google/gson/stream/JsonReader;)Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlImpl;->(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlImpl;->openConnection()Lio/embrace/android/embracesdk/comms/api/EmbraceConnection;
+HSPLio/embrace/android/embracesdk/comms/api/EmbraceUrlImpl;->toString()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$CachedSession;->(Ljava/lang/String;J)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$CachedSession;->(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$CachedSession;->getFilename()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$CachedSession;->getSessionId()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$Companion;->()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$deletePayload$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$deletePayload$1;->run()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$loadFailedApiCalls$cached$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$loadFailedApiCalls$cached$1;->call()Lio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$loadFailedApiCalls$cached$1;->call()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$saveBytes$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;Ljava/lang/String;[B)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$saveBytes$1;->run()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$saveFailedApiCalls$$inlined$let$lambda$1;->([BLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$saveFailedApiCalls$$inlined$let$lambda$1;->run()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$sessionMessageSerializer$2;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$sessionMessageSerializer$2;->invoke()Lio/embrace/android/embracesdk/session/SessionMessageSerializer;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager$sessionMessageSerializer$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->(Lio/embrace/android/embracesdk/comms/delivery/CacheService;Ljava/util/concurrent/ExecutorService;Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;Lio/embrace/android/embracesdk/clock/Clock;Lio/embrace/android/embracesdk/internal/EmbraceSerializer;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->access$getCacheService$p(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)Lio/embrace/android/embracesdk/comms/delivery/CacheService;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->access$getCachedSessions$p(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)Ljava/util/Map;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->access$getClock$p(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)Lio/embrace/android/embracesdk/clock/Clock;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->access$getLogger$p(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)Lio/embrace/android/embracesdk/logging/InternalEmbraceLogger;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->access$getSerializer$p(Lio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;)Lio/embrace/android/embracesdk/internal/EmbraceSerializer;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->deletePayload(Ljava/lang/String;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->getAllCachedSessionIds()Ljava/util/List;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->getSessionMessageSerializer()Lio/embrace/android/embracesdk/session/SessionMessageSerializer;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->loadCrash()Lio/embrace/android/embracesdk/payload/EventMessage;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->loadFailedApiCalls()Lio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->loadPayload(Ljava/lang/String;)[B
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->saveBytes(Ljava/lang/String;[B)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->saveFailedApiCalls(Lio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryCacheManager;->saveSession(Lio/embrace/android/embracesdk/payload/SessionMessage;)[B
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCall;->getApiRequest()Lio/embrace/android/embracesdk/comms/api/ApiRequest;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCall;->getCachedPayload()Ljava/lang/String;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;->()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;->getSize()I
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;->size()I
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$1;->invoke()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$createRequest$url$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$createRequest$url$1;->invoke()Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$createRequest$url$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$postOnExecutor$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;ZLio/embrace/android/embracesdk/comms/api/ApiRequest;[BLkotlin/jvm/functions/Function0;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$postOnExecutor$1;->run()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$retryQueue$2;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$retryQueue$2;->invoke()Lio/embrace/android/embracesdk/comms/delivery/DeliveryFailedApiCalls;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$retryQueue$2;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$scheduleFailedApiCallsRetry$$inlined$synchronized$lambda$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;J)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$scheduleFailedApiCallsRetry$$inlined$synchronized$lambda$1;->run()V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendLogs$url$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendLogs$url$1;->invoke()Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendLogs$url$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendSession$url$1;->(Lio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;)V
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendSession$url$1;->invoke()Lio/embrace/android/embracesdk/comms/api/EmbraceUrl;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager$sendSession$url$1;->invoke()Ljava/lang/Object;
+HSPLio/embrace/android/embracesdk/comms/delivery/DeliveryNetworkManager;->