From 14c083ae2435e14d57e68a65b254a2de80126cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Wold=C5=99ich?= <31292499+krystofwoldrich@users.noreply.github.com> Date: Thu, 2 Feb 2023 11:37:55 +0100 Subject: [PATCH] chore(internal): Add snapshot view hierarchy as byte array (#2508) --- .../api/sentry-android-core.api | 1 + .../core/ViewHierarchyEventProcessor.java | 26 +++++++++++ .../core/ViewHierarchyEventProcessorTest.kt | 43 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index fcd2c080e9..f406706e92 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -266,6 +266,7 @@ public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentr public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent; public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy; public static fun snapshotViewHierarchy (Landroid/view/View;)Lio/sentry/protocol/ViewHierarchy; + public static fun snapshotViewHierarchyAsData (Landroid/app/Activity;Lio/sentry/ISerializer;Lio/sentry/ILogger;)[B } public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java index ea3a95c71b..b2b79b1742 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java @@ -8,11 +8,13 @@ import io.sentry.EventProcessor; import io.sentry.Hint; import io.sentry.ILogger; +import io.sentry.ISerializer; import io.sentry.SentryEvent; import io.sentry.SentryLevel; import io.sentry.android.core.internal.gestures.ViewUtils; import io.sentry.protocol.ViewHierarchy; import io.sentry.protocol.ViewHierarchyNode; +import io.sentry.util.JsonSerializationUtils; import io.sentry.util.Objects; import java.util.ArrayList; import java.util.List; @@ -52,6 +54,30 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) return event; } + @Nullable + public static byte[] snapshotViewHierarchyAsData( + @Nullable Activity activity, @NotNull ISerializer serializer, @NotNull ILogger logger) { + @Nullable ViewHierarchy viewHierarchy = snapshotViewHierarchy(activity, logger); + + if (viewHierarchy == null) { + logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy."); + return null; + } + + final @Nullable byte[] bytes = + JsonSerializationUtils.bytesFrom(serializer, logger, viewHierarchy); + if (bytes == null) { + logger.log(SentryLevel.ERROR, "Could not serialize ViewHierarchy."); + return null; + } + if (bytes.length < 1) { + logger.log(SentryLevel.ERROR, "Got empty bytes array after serializing ViewHierarchy."); + return null; + } + + return bytes; + } + @Nullable public static ViewHierarchy snapshotViewHierarchy( @Nullable Activity activity, @NotNull ILogger logger) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ViewHierarchyEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ViewHierarchyEventProcessorTest.kt index 4fd03b761b..b06e9c8cb3 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ViewHierarchyEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ViewHierarchyEventProcessorTest.kt @@ -6,20 +6,40 @@ import android.view.ViewGroup import android.view.Window import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Hint +import io.sentry.JsonSerializable +import io.sentry.JsonSerializer import io.sentry.SentryEvent import io.sentry.protocol.SentryException import org.junit.runner.RunWith +import org.mockito.invocation.InvocationOnMock +import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import java.io.Writer import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull @RunWith(AndroidJUnit4::class) class ViewHierarchyEventProcessorTest { private class Fixture { + val logger = mock() + val serializer: JsonSerializer = mock { + on(it.serialize(any(), any())).then { invocationOnMock: InvocationOnMock -> + val writer: Writer = invocationOnMock.getArgument(1) + writer.write("mock-data") + writer.flush() + } + } + val emptySerializer: JsonSerializer = mock { + on(it.serialize(any(), any())).then { invocationOnMock: InvocationOnMock -> + val writer: Writer = invocationOnMock.getArgument(1) + writer.flush() + } + } val activity = mock() val window = mock() val view = mock() @@ -61,6 +81,29 @@ class ViewHierarchyEventProcessorTest { fixture = Fixture() } + @Test + fun `should return a view hierarchy as byte array`() { + val viewHierarchy = ViewHierarchyEventProcessor.snapshotViewHierarchyAsData( + fixture.activity, + fixture.serializer, + fixture.logger + ) + + assertNotNull(viewHierarchy) + assertFalse(viewHierarchy.isEmpty()) + } + + @Test + fun `should return null as bytes are empty array`() { + val viewHierarchy = ViewHierarchyEventProcessor.snapshotViewHierarchyAsData( + fixture.activity, + fixture.emptySerializer, + fixture.logger + ) + + assertNull(viewHierarchy) + } + @Test fun `when an event errored, the view hierarchy should not attached if the feature is disabled`() { val (event, hint) = fixture.process(