diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f43332c4..4e6bc7c535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - For example, (this is already a default behavior) to redact all `TextView`s and their subclasses (`RadioButton`, `EditText`, etc.): `options.experimental.sessionReplay.addRedactViewClass("android.widget.TextView")` - If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified - Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669)) +- Fix potential ANRs due to `Calendar.getInstance` usage in Breadcrumbs constructor ([#3736](https://github.com/getsentry/sentry-java/pull/3736)) *Breaking changes*: diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 5f8b061acf..530d8241f5 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -98,6 +98,7 @@ public final class io/sentry/BaggageHeader { public final class io/sentry/Breadcrumb : io/sentry/JsonSerializable, io/sentry/JsonUnknown { public fun ()V + public fun (J)V public fun (Ljava/lang/String;)V public fun (Ljava/util/Date;)V public static fun debug (Ljava/lang/String;)Lio/sentry/Breadcrumb; diff --git a/sentry/src/main/java/io/sentry/Breadcrumb.java b/sentry/src/main/java/io/sentry/Breadcrumb.java index 954c57c36a..10b3c951d3 100644 --- a/sentry/src/main/java/io/sentry/Breadcrumb.java +++ b/sentry/src/main/java/io/sentry/Breadcrumb.java @@ -19,8 +19,11 @@ /** Series of application events */ public final class Breadcrumb implements JsonUnknown, JsonSerializable { - /** A timestamp representing when the breadcrumb occurred. */ - private final @NotNull Date timestamp; + /** A timestamp representing when the breadcrumb occurred in milliseconds. */ + private @Nullable final Long timestampMs; + + /** A timestamp representing when the breadcrumb occurred as java.util.Date. */ + private @Nullable Date timestamp; /** If a message is provided, its rendered as text and the whitespace is preserved. */ private @Nullable String message; @@ -51,12 +54,20 @@ public final class Breadcrumb implements JsonUnknown, JsonSerializable { * * @param timestamp the timestamp */ + @SuppressWarnings("JavaUtilDate") public Breadcrumb(final @NotNull Date timestamp) { this.timestamp = timestamp; + this.timestampMs = null; + } + + public Breadcrumb(final long timestamp) { + this.timestampMs = timestamp; + this.timestamp = null; } Breadcrumb(final @NotNull Breadcrumb breadcrumb) { this.timestamp = breadcrumb.timestamp; + this.timestampMs = breadcrumb.timestampMs; this.message = breadcrumb.message; this.type = breadcrumb.type; this.category = breadcrumb.category; @@ -504,7 +515,7 @@ public static Breadcrumb fromMap( /** Breadcrumb ctor */ public Breadcrumb() { - this(DateUtils.getCurrentDateTime()); + this(System.currentTimeMillis()); } /** @@ -518,13 +529,20 @@ public Breadcrumb(@Nullable String message) { } /** - * Returns the Breadcrumb's timestamp + * Returns the Breadcrumb's timestamp as java.util.Date * * @return the timestamp */ - @SuppressWarnings({"JdkObsolete", "JavaUtilDate"}) + @SuppressWarnings("JavaUtilDate") public @NotNull Date getTimestamp() { - return (Date) timestamp.clone(); + if (timestamp != null) { + return (Date) timestamp.clone(); + } else if (timestampMs != null) { + // we memoize it here into timestamp to avoid instantiating Calendar again and again + timestamp = DateUtils.getDateTime(timestampMs); + return timestamp; + } + throw new IllegalStateException("No timestamp set for breadcrumb"); } /** @@ -664,7 +682,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Breadcrumb that = (Breadcrumb) o; - return timestamp.getTime() == that.timestamp.getTime() + return getTimestamp().getTime() == that.getTimestamp().getTime() && Objects.equals(message, that.message) && Objects.equals(type, that.type) && Objects.equals(category, that.category) @@ -704,7 +722,7 @@ public static final class JsonKeys { public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) throws IOException { writer.beginObject(); - writer.name(JsonKeys.TIMESTAMP).value(logger, timestamp); + writer.name(JsonKeys.TIMESTAMP).value(logger, getTimestamp()); if (message != null) { writer.name(JsonKeys.MESSAGE).value(message); } diff --git a/sentry/src/test/java/io/sentry/BreadcrumbTest.kt b/sentry/src/test/java/io/sentry/BreadcrumbTest.kt index 048d761799..bac143812a 100644 --- a/sentry/src/test/java/io/sentry/BreadcrumbTest.kt +++ b/sentry/src/test/java/io/sentry/BreadcrumbTest.kt @@ -1,5 +1,6 @@ package io.sentry +import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -100,6 +101,12 @@ class BreadcrumbTest { assertNotNull(breadcrumb.timestamp) } + @Test + fun `breadcrumb can be created with Date timestamp`() { + val breadcrumb = Breadcrumb(Date(123L)) + assertEquals(123L, breadcrumb.timestamp.time) + } + @Test fun `breadcrumb takes message on ctor`() { val breadcrumb = Breadcrumb("this is a test")