Skip to content

Commit

Permalink
Merge df56bd1 into 95647bd
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi authored Dec 29, 2022
2 parents 95647bd + df56bd1 commit 4f4ea04
Show file tree
Hide file tree
Showing 21 changed files with 1,084 additions and 28 deletions.
8 changes: 8 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isAnrEnabled ()Z
public fun isAnrReportInDebug ()Z
public fun isAttachScreenshot ()Z
public fun isAttachViewHierarchy ()Z
public fun isCollectAdditionalContext ()Z
public fun isEnableActivityLifecycleBreadcrumbs ()Z
public fun isEnableActivityLifecycleTracingAutoFinish ()Z
Expand All @@ -164,6 +165,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setAnrReportInDebug (Z)V
public fun setAnrTimeoutIntervalMillis (J)V
public fun setAttachScreenshot (Z)V
public fun setAttachViewHierarchy (Z)V
public fun setCollectAdditionalContext (Z)V
public fun setDebugImagesLoader (Lio/sentry/android/core/IDebugImagesLoader;)V
public fun setEnableActivityLifecycleBreadcrumbs (Z)V
Expand Down Expand Up @@ -235,6 +237,12 @@ public final class io/sentry/android/core/UserInteractionIntegration : android/a
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentry/EventProcessor {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
public static fun snapshotViewHierarchy (Landroid/app/Activity;)Lio/sentry/protocol/ViewHierarchy;
}

public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun getDirectory ()Ljava/io/File;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ private static void installDefaultIntegrations(
SentryLevel.WARNING,
"ActivityLifecycle, FragmentLifecycle and UserInteraction Integrations need an Application class to be installed.");
}
options.addEventProcessor(new ViewHierarchyEventProcessor(options));

if (isTimberAvailable) {
options.addIntegration(new SentryTimberIntegration());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ final class ManifestMetadataReader {
static final String IDLE_TIMEOUT = "io.sentry.traces.idle-timeout";

static final String ATTACH_SCREENSHOT = "io.sentry.attach-screenshot";
static final String ATTACH_VIEW_HIERARCHY = "io.sentry.attach-view-hierarchy";
static final String CLIENT_REPORTS_ENABLE = "io.sentry.send-client-reports";
static final String COLLECT_ADDITIONAL_CONTEXT = "io.sentry.additional-context";

Expand Down Expand Up @@ -220,6 +221,9 @@ static void applyMetadata(
options.setAttachScreenshot(
readBool(metadata, logger, ATTACH_SCREENSHOT, options.isAttachScreenshot()));

options.setAttachViewHierarchy(
readBool(metadata, logger, ATTACH_VIEW_HIERARCHY, options.isAttachViewHierarchy()));

options.setSendClientReports(
readBool(metadata, logger, CLIENT_REPORTS_ENABLE, options.isSendClientReports()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public final class SentryAndroidOptions extends SentryOptions {
/** Enables or disables the attach screenshot feature when an error happened. */
private boolean attachScreenshot;

/** Enables or disables the attach view hierarchy feature when an error happened. */
private boolean attachViewHierarchy;

/**
* Enables or disables collecting of device information which requires Inter-Process Communication
* (IPC)
Expand Down Expand Up @@ -329,6 +332,14 @@ public void setAttachScreenshot(boolean attachScreenshot) {
this.attachScreenshot = attachScreenshot;
}

public boolean isAttachViewHierarchy() {
return attachViewHierarchy;
}

public void setAttachViewHierarchy(boolean attachViewHierarchy) {
this.attachViewHierarchy = attachViewHierarchy;
}

public boolean isCollectAdditionalContext() {
return collectAdditionalContext;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package io.sentry.android.core;

import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import io.sentry.Attachment;
import io.sentry.EventProcessor;
import io.sentry.Hint;
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.Objects;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** ViewHierarchyEventProcessor responsible for taking a snapshot of the current view hierarchy. */
@ApiStatus.Internal
public final class ViewHierarchyEventProcessor implements EventProcessor {

private final @NotNull SentryAndroidOptions options;

public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options) {
this.options = Objects.requireNonNull(options, "SentryAndroidOptions is required");
}

@Override
public @NotNull SentryEvent process(final @NotNull SentryEvent event, @NotNull Hint hint) {
if (!event.isErrored()) {
return event;
}

if (!options.isAttachViewHierarchy()) {
this.options.getLogger().log(SentryLevel.DEBUG, "attachViewHierarchy is disabled.");
return event;
}

final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity();
if (activity == null) {
return event;
}

try {
final @NotNull ViewHierarchy viewHierarchy = snapshotViewHierarchy(activity);
@SuppressWarnings("CharsetObjectCanBeUsed")
final Charset UTF8 = Charset.forName("UTF-8");

try (final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF8))) {

options.getSerializer().serialize(viewHierarchy, writer);

final Attachment attachment = Attachment.fromViewHierarchy(stream.toByteArray());
hint.setViewHierarchy(attachment);
}
} catch (Throwable t) {
options.getLogger().log(SentryLevel.ERROR, "Could not snapshot ViewHierarchy", t);
}

return event;
}

@NotNull
public static ViewHierarchy snapshotViewHierarchy(Activity activity) {
final List<ViewHierarchyNode> windows = new ArrayList<>();
final ViewHierarchy viewHierarchy = new ViewHierarchy("android_view_system", windows);

final @Nullable View decorView = activity.getWindow().peekDecorView();
if (decorView == null) {
return viewHierarchy;
}

final @NotNull ViewHierarchyNode decorNode = viewToNode(decorView);
windows.add(decorNode);
addChildren(decorView, decorNode);

return viewHierarchy;
}

private static void addChildren(@NotNull View view, @NotNull ViewHierarchyNode parentNode) {
if (!(view instanceof ViewGroup)) {
return;
}

final @NotNull ViewGroup viewGroup = ((ViewGroup) view);
final int childCount = viewGroup.getChildCount();
if (childCount == 0) {
return;
}

final @NotNull List<ViewHierarchyNode> childNodes = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
final @Nullable View child = viewGroup.getChildAt(i);
if (child != null) {
final @NotNull ViewHierarchyNode childNode = viewToNode(child);
childNodes.add(childNode);
addChildren(child, childNode);
}
}
parentNode.setChildren(childNodes);
}

@NotNull
private static ViewHierarchyNode viewToNode(final View view) {
@NotNull final ViewHierarchyNode node = new ViewHierarchyNode();

@Nullable String className = view.getClass().getCanonicalName();
if (className == null) {
className = view.getClass().getSimpleName();
}
node.setType(className);

try {
final String identifier = ViewUtils.getResourceId(view);
node.setIdentifier(identifier);
} catch (Exception e) {
// ignored
}
node.setX((double) view.getX());
node.setY((double) view.getY());
node.setWidth((double) view.getWidth());
node.setHeight((double) view.getHeight());
node.setAlpha((double) view.getAlpha());
node.setVisible(view.getVisibility() == View.VISIBLE);

return node;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import io.sentry.util.Objects;
import java.util.LinkedList;
import java.util.Queue;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

final class ViewUtils {
public final class ViewUtils {

/**
* Finds a target view, that has been selected/clicked by the given coordinates x and y and the
Expand Down Expand Up @@ -85,7 +86,8 @@ static String getResourceIdWithFallback(final @NotNull View view) {
* @return human-readable view id
* @throws Resources.NotFoundException in case the view id was not found
*/
static String getResourceId(final @NotNull View view) throws Resources.NotFoundException {
@ApiStatus.Internal
public static String getResourceId(final @NotNull View view) throws Resources.NotFoundException {
final int viewId = view.getId();
final Resources resources = view.getContext().getResources();
String resourceId = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,19 @@ class ManifestMetadataReaderTest {
assertTrue(fixture.options.isAttachScreenshot)
}

@Test
fun `applyMetadata reads attach viewhierarchy to options`() {
// Arrange
val bundle = bundleOf(ManifestMetadataReader.ATTACH_VIEW_HIERARCHY to true)
val context = fixture.getContext(metaData = bundle)

// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertTrue(fixture.options.isAttachViewHierarchy)
}

@Test
fun `applyMetadata reads attach screenshots and keep default value if not found`() {
// Arrange
Expand Down
Loading

0 comments on commit 4f4ea04

Please sign in to comment.