From bd604641a7cef522e9bf066faf1f6be2ae666d38 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 19 Oct 2023 11:02:48 +0200 Subject: [PATCH 1/8] Add current activity name to app context --- .../core/ActivityLifecycleIntegration.java | 5 +++ .../android/core/InternalSentrySdk.java | 24 ++++++------ .../core/ViewHierarchyEventProcessor.java | 6 +-- .../AndroidViewGestureTargetLocator.java | 6 +-- .../android/core/internal/util/ClassUtil.java | 19 +++++++++ .../core/ActivityLifecycleIntegrationTest.kt | 33 ++++++++++++++-- .../core/internal/util/ClassUtilTest.kt | 34 ++++++++++++++++ sentry/api/sentry.api | 5 +++ sentry/src/main/java/io/sentry/Scope.java | 38 ++++++++++++++++++ .../src/main/java/io/sentry/protocol/App.java | 39 ++++++++++++++++++- .../test/java/io/sentry/protocol/AppTest.kt | 9 +++++ 11 files changed, 192 insertions(+), 26 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ClassUtilTest.kt diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index ee4ea10b72..12eea7922a 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -28,6 +28,7 @@ import io.sentry.SpanStatus; import io.sentry.TransactionContext; import io.sentry.TransactionOptions; +import io.sentry.android.core.internal.util.ClassUtil; import io.sentry.android.core.internal.util.FirstDrawDoneListener; import io.sentry.protocol.MeasurementValue; import io.sentry.protocol.TransactionNameSource; @@ -372,6 +373,10 @@ public synchronized void onActivityCreated( final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) { setColdStart(savedInstanceState); addBreadcrumb(activity, "created"); + if (hub != null) { + final @Nullable String activityClassName = ClassUtil.getClassName(activity); + hub.configureScope(scope -> scope.setScreen(activityClassName)); + } startTracing(activity); final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity); diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index eac29bc42d..bf88e96902 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -96,19 +96,19 @@ public static Map serializeScope( @Nullable App app = scope.getContexts().getApp(); if (app == null) { app = new App(); - app.setAppName(ContextUtils.getApplicationName(context, options.getLogger())); - app.setAppStartTime(DateUtils.toUtilDate(AppStartState.getInstance().getAppStartTime())); - - final @NotNull BuildInfoProvider buildInfoProvider = - new BuildInfoProvider(options.getLogger()); - final @Nullable PackageInfo packageInfo = - ContextUtils.getPackageInfo( - context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider); - if (packageInfo != null) { - ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app); - } - scope.getContexts().setApp(app); } + app.setAppName(ContextUtils.getApplicationName(context, options.getLogger())); + app.setAppStartTime(DateUtils.toUtilDate(AppStartState.getInstance().getAppStartTime())); + + final @NotNull BuildInfoProvider buildInfoProvider = + new BuildInfoProvider(options.getLogger()); + final @Nullable PackageInfo packageInfo = + ContextUtils.getPackageInfo( + context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider); + if (packageInfo != null) { + ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app); + } + scope.getContexts().setApp(app); writer.name("user").value(logger, scope.getUser()); writer.name("contexts").value(logger, scope.getContexts()); 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 2e1306cca8..54ef405b47 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 @@ -15,6 +15,7 @@ import io.sentry.android.core.internal.gestures.ViewUtils; import io.sentry.android.core.internal.util.AndroidCurrentDateProvider; import io.sentry.android.core.internal.util.AndroidMainThreadChecker; +import io.sentry.android.core.internal.util.ClassUtil; import io.sentry.android.core.internal.util.Debouncer; import io.sentry.internal.viewhierarchy.ViewHierarchyExporter; import io.sentry.protocol.ViewHierarchy; @@ -240,10 +241,7 @@ private static void addChildren( private static ViewHierarchyNode viewToNode(@NotNull final View view) { @NotNull final ViewHierarchyNode node = new ViewHierarchyNode(); - @Nullable String className = view.getClass().getCanonicalName(); - if (className == null) { - className = view.getClass().getSimpleName(); - } + @Nullable String className = ClassUtil.getClassName(view); node.setType(className); try { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java index 517c0224c9..945ebeef64 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java @@ -5,6 +5,7 @@ import android.widget.AbsListView; import android.widget.ScrollView; import androidx.core.view.ScrollingView; +import io.sentry.android.core.internal.util.ClassUtil; import io.sentry.internal.gestures.GestureTargetLocator; import io.sentry.internal.gestures.UiElement; import org.jetbrains.annotations.ApiStatus; @@ -44,10 +45,7 @@ && isViewScrollable(view, isAndroidXAvailable)) { private UiElement createUiElement(final @NotNull View targetView) { try { final String resourceName = ViewUtils.getResourceId(targetView); - @Nullable String className = targetView.getClass().getCanonicalName(); - if (className == null) { - className = targetView.getClass().getSimpleName(); - } + @Nullable String className = ClassUtil.getClassName(targetView); return new UiElement(targetView, className, resourceName, null, ORIGIN); } catch (Resources.NotFoundException ignored) { return null; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java new file mode 100644 index 0000000000..8b730e267a --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ClassUtil.java @@ -0,0 +1,19 @@ +package io.sentry.android.core.internal.util; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public class ClassUtil { + + public static @Nullable String getClassName(final @Nullable Object object) { + if (object == null) { + return null; + } + final @Nullable String canonicalName = object.getClass().getCanonicalName(); + if (canonicalName != null) { + return canonicalName; + } + return object.getClass().getSimpleName(); + } +} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index a07f6692d6..26bd42e5ab 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -1405,10 +1405,31 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.hub, fixture.options) sut.onActivityCreated(activity, fixture.bundle) - verify(fixture.hub).configureScope(any()) + // once for the screen, and once for the tracing propagation context + verify(fixture.hub, times(2)).configureScope(any()) assertNotSame(propagationContextAtStart, scope.propagationContext) } + @Test + fun `sets the activity as the current screen`() { + val sut = fixture.getSut() + val activity = mock() + fixture.options.enableTracing = false + + val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java) + val scope = mock() + whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer { + argumentCaptor.value.run(scope) + } + + sut.register(fixture.hub, fixture.options) + sut.onActivityCreated(activity, fixture.bundle) + + // once for the screen, and once for the tracing propagation context + verify(fixture.hub, times(2)).configureScope(any()) + verify(scope).setScreen(any()) + } + @Test fun `does not start another new trace if one has already been started but does after activity was destroyed`() { val sut = fixture.getSut() @@ -1425,21 +1446,25 @@ class ActivityLifecycleIntegrationTest { sut.register(fixture.hub, fixture.options) sut.onActivityCreated(activity, fixture.bundle) - verify(fixture.hub).configureScope(any()) + // once for the screen, and once for the tracing propagation context + verify(fixture.hub, times(2)).configureScope(any()) + val propagationContextAfterNewTrace = scope.propagationContext assertNotSame(propagationContextAtStart, propagationContextAfterNewTrace) clearInvocations(fixture.hub) sut.onActivityCreated(activity, fixture.bundle) - verify(fixture.hub, never()).configureScope(any()) + // once for the screen, but not for the tracing propagation context + verify(fixture.hub).configureScope(any()) assertSame(propagationContextAfterNewTrace, scope.propagationContext) sut.onActivityDestroyed(activity) clearInvocations(fixture.hub) sut.onActivityCreated(activity, fixture.bundle) - verify(fixture.hub).configureScope(any()) + // once for the screen, and once for the tracing propagation context + verify(fixture.hub, times(2)).configureScope(any()) assertNotSame(propagationContextAfterNewTrace, scope.propagationContext) } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ClassUtilTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ClassUtilTest.kt new file mode 100644 index 0000000000..a8ca6a534f --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ClassUtilTest.kt @@ -0,0 +1,34 @@ +package io.sentry.android.core.internal.util + +import java.util.concurrent.Callable +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class ClassUtilTest { + + class Outer { + class Inner { + val x: Callable = Callable { false } + } + } + + @Test + fun `getClassName returns cannonical name by default`() { + val name = ClassUtil.getClassName(Outer.Inner()) + assertEquals("io.sentry.android.core.internal.util.ClassUtilTest.Outer.Inner", name) + } + + @Test + fun `getClassName falls back to simple name for anonymous classes`() { + val name = ClassUtil.getClassName(Outer.Inner().x) + assertTrue(name!!.contains("$")) + } + + @Test + fun `getClassName returns null when obj is null`() { + val name = ClassUtil.getClassName(null) + assertNull(name) + } +} diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index bb00fb41af..2fce956d47 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1385,6 +1385,7 @@ public final class io/sentry/Scope { public fun getLevel ()Lio/sentry/SentryLevel; public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRequest ()Lio/sentry/protocol/Request; + public fun getScreen ()Ljava/lang/String; public fun getSession ()Lio/sentry/Session; public fun getSpan ()Lio/sentry/ISpan; public fun getTags ()Ljava/util/Map; @@ -1406,6 +1407,7 @@ public final class io/sentry/Scope { public fun setLevel (Lio/sentry/SentryLevel;)V public fun setPropagationContext (Lio/sentry/PropagationContext;)V public fun setRequest (Lio/sentry/protocol/Request;)V + public fun setScreen (Ljava/lang/String;)V public fun setTag (Ljava/lang/String;Ljava/lang/String;)V public fun setTransaction (Lio/sentry/ITransaction;)V public fun setTransaction (Ljava/lang/String;)V @@ -3105,6 +3107,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr public fun getInForeground ()Ljava/lang/Boolean; public fun getPermissions ()Ljava/util/Map; public fun getUnknown ()Ljava/util/Map; + public fun getViewNames ()Ljava/util/List; public fun hashCode ()I public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setAppBuild (Ljava/lang/String;)V @@ -3117,6 +3120,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr public fun setInForeground (Ljava/lang/Boolean;)V public fun setPermissions (Ljava/util/Map;)V public fun setUnknown (Ljava/util/Map;)V + public fun setViewNames (Ljava/util/List;)V } public final class io/sentry/protocol/App$Deserializer : io/sentry/JsonDeserializer { @@ -3135,6 +3139,7 @@ public final class io/sentry/protocol/App$JsonKeys { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEVICE_APP_HASH Ljava/lang/String; public static final field IN_FOREGROUND Ljava/lang/String; + public static final field VIEW_NAMES Ljava/lang/String; public fun ()V } diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index bf4c92c178..af3450bc08 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -1,5 +1,6 @@ package io.sentry; +import io.sentry.protocol.App; import io.sentry.protocol.Contexts; import io.sentry.protocol.Request; import io.sentry.protocol.TransactionNameSource; @@ -33,6 +34,9 @@ public final class Scope { /** Scope's user */ private @Nullable User user; + /** Scope's screen */ + private @Nullable String screen; + /** Scope's request */ private @Nullable Request request; @@ -97,6 +101,7 @@ public Scope(final @NotNull Scope scope) { final User userRef = scope.user; this.user = userRef != null ? new User(userRef) : null; + this.screen = scope.screen; final Request requestRef = scope.request; this.request = requestRef != null ? new Request(requestRef) : null; @@ -259,6 +264,39 @@ public void setUser(final @Nullable User user) { } } + /** + * Returns the Scope's current screen, previously set by {@link Scope#setScreen(String)} + * + * @return the name of the screen + */ + public @Nullable String getScreen() { + return screen; + } + + /** + * Sets the Scope's current screen + * + * @param screen the name of the screen + */ + public void setScreen(final @Nullable String screen) { + this.screen = screen; + + final @NotNull Contexts contexts = getContexts(); + @Nullable App app = contexts.getApp(); + if (app == null) { + app = new App(); + contexts.setApp(app); + } + + if (screen == null) { + app.setViewNames(null); + } else { + final @NotNull List viewNames = new ArrayList<>(1); + viewNames.add(screen); + app.setViewNames(viewNames); + } + } + /** * Returns the Scope's request * diff --git a/sentry/src/main/java/io/sentry/protocol/App.java b/sentry/src/main/java/io/sentry/protocol/App.java index b025d21e7e..b7b41638db 100644 --- a/sentry/src/main/java/io/sentry/protocol/App.java +++ b/sentry/src/main/java/io/sentry/protocol/App.java @@ -11,6 +11,7 @@ import io.sentry.vendor.gson.stream.JsonToken; import java.io.IOException; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.NotNull; @@ -39,6 +40,8 @@ public final class App implements JsonUnknown, JsonSerializable { private @Nullable String appBuild; /** Application permissions in the form of "permission_name" : "granted|not_granted" */ private @Nullable Map permissions; + /** The list of the visibile UI screens * */ + private @Nullable List viewNames; /** * A flag indicating whether the app is in foreground or not. An app is in foreground when it's * visible to the user. @@ -57,6 +60,7 @@ public App() {} this.deviceAppHash = app.deviceAppHash; this.permissions = CollectionUtils.newConcurrentHashMap(app.permissions); this.inForeground = app.inForeground; + this.viewNames = CollectionUtils.newArrayList(app.viewNames); this.unknown = CollectionUtils.newConcurrentHashMap(app.unknown); } @@ -138,6 +142,15 @@ public void setInForeground(final @Nullable Boolean inForeground) { this.inForeground = inForeground; } + @Nullable + public List getViewNames() { + return viewNames; + } + + public void setViewNames(final @Nullable List viewNames) { + this.viewNames = viewNames; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -149,13 +162,25 @@ public boolean equals(Object o) { && Objects.equals(buildType, app.buildType) && Objects.equals(appName, app.appName) && Objects.equals(appVersion, app.appVersion) - && Objects.equals(appBuild, app.appBuild); + && Objects.equals(appBuild, app.appBuild) + && Objects.equals(permissions, app.permissions) + && Objects.equals(inForeground, app.inForeground) + && Objects.equals(viewNames, app.viewNames); } @Override public int hashCode() { return Objects.hash( - appIdentifier, appStartTime, deviceAppHash, buildType, appName, appVersion, appBuild); + appIdentifier, + appStartTime, + deviceAppHash, + buildType, + appName, + appVersion, + appBuild, + permissions, + inForeground, + viewNames); } // region json @@ -181,6 +206,7 @@ public static final class JsonKeys { public static final String APP_BUILD = "app_build"; public static final String APP_PERMISSIONS = "permissions"; public static final String IN_FOREGROUND = "in_foreground"; + public static final String VIEW_NAMES = "view_names"; } @Override @@ -214,6 +240,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (inForeground != null) { writer.name(JsonKeys.IN_FOREGROUND).value(inForeground); } + if (viewNames != null) { + writer.name(JsonKeys.VIEW_NAMES).value(logger, viewNames); + } if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -263,6 +292,12 @@ public static final class Deserializer implements JsonDeserializer { case JsonKeys.IN_FOREGROUND: app.inForeground = reader.nextBooleanOrNull(); break; + case JsonKeys.VIEW_NAMES: + final @Nullable List viewNames = (List) reader.nextObjectOrNull(); + if (viewNames != null) { + app.setViewNames(viewNames); + } + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); diff --git a/sentry/src/test/java/io/sentry/protocol/AppTest.kt b/sentry/src/test/java/io/sentry/protocol/AppTest.kt index 0a2c301615..3f51594fff 100644 --- a/sentry/src/test/java/io/sentry/protocol/AppTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/AppTest.kt @@ -18,6 +18,8 @@ class AppTest { app.appVersion = "app version" app.buildType = "build type" app.deviceAppHash = "device app hash" + app.permissions = mapOf(Pair("internet", "granted")) + app.viewNames = listOf("MainActivity") app.inForeground = true val unknown = mapOf(Pair("unknown", "unknown")) app.unknown = unknown @@ -27,6 +29,8 @@ class AppTest { assertNotNull(clone) assertNotSame(app, clone) assertNotSame(app.appStartTime, clone.appStartTime) + assertNotSame(app.permissions, clone.permissions) + assertNotSame(app.viewNames, clone.viewNames) assertNotSame(app.unknown, clone.unknown) } @@ -42,6 +46,8 @@ class AppTest { app.appVersion = "app version" app.buildType = "build type" app.deviceAppHash = "device app hash" + app.permissions = mapOf(Pair("internet", "granted")) + app.viewNames = listOf("MainActivity") app.inForeground = true val unknown = mapOf(Pair("unknown", "unknown")) app.unknown = unknown @@ -57,6 +63,9 @@ class AppTest { assertEquals("app version", clone.appVersion) assertEquals("build type", clone.buildType) assertEquals("device app hash", clone.deviceAppHash) + assertEquals(mapOf(Pair("internet", "granted")), clone.permissions) + assertEquals(listOf("MainActivity"), clone.viewNames) + assertEquals(true, clone.inForeground) assertNotNull(clone.unknown) { assertEquals("unknown", it["unknown"]) From 09dfd0d1ffd20344698751666ea122c3831f7699 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 19 Oct 2023 11:19:29 +0200 Subject: [PATCH 2/8] Add missing tests --- sentry/src/main/java/io/sentry/Scope.java | 1 + sentry/src/test/java/io/sentry/ScopeTest.kt | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index af3450bc08..a24e72611c 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -464,6 +464,7 @@ public void clear() { level = null; user = null; request = null; + screen = null; fingerprint.clear(); clearBreadcrumbs(); tags.clear(); diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 628c15cad9..22578e7a76 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -65,6 +65,8 @@ class ScopeTest { scope.setTag("tag", "tag") scope.setExtra("extra", "extra") + scope.screen = "MainActivity" + val processor = CustomEventProcessor() scope.addEventProcessor(processor) @@ -117,6 +119,8 @@ class ScopeTest { val attachment = Attachment("path/log.txt") scope.addAttachment(attachment) + scope.screen = "MainActivity" + scope.setContexts("contexts", "contexts") val clone = Scope(scope) @@ -133,6 +137,8 @@ class ScopeTest { assertEquals("transaction-name", (clone.span as SentryTracer).name) assertEquals("tag", clone.tags["tag"]) + assertEquals("MainActivity", clone.screen) + assertEquals("extra", clone.extras["extra"]) assertEquals("contexts", (clone.contexts["contexts"] as HashMap<*, *>)["value"]) assertEquals(transaction, clone.span) @@ -168,6 +174,7 @@ class ScopeTest { scope.addBreadcrumb(breadcrumb) scope.setTag("tag", "tag") scope.setExtra("extra", "extra") + scope.screen = "MainActivity" val processor = CustomEventProcessor() scope.addEventProcessor(processor) @@ -198,6 +205,7 @@ class ScopeTest { scope.setTag("otherTag", "otherTag") scope.setExtra("extra", "newExtra") scope.setExtra("otherExtra", "otherExtra") + scope.screen = "LoginActivity" scope.addEventProcessor(processor) @@ -216,6 +224,7 @@ class ScopeTest { assertEquals("tag", clone.tags["tag"]) assertEquals(1, clone.tags.size) assertEquals("extra", clone.extras["extra"]) + assertEquals("MainActivity", clone.screen) assertEquals(1, clone.extras.size) assertEquals(1, clone.eventProcessors.size) assertNull(clone.span) @@ -261,6 +270,7 @@ class ScopeTest { scope.fingerprint = mutableListOf("finger") scope.addBreadcrumb(Breadcrumb()) scope.setTag("some", "tag") + scope.screen = "MainActivity" scope.setExtra("some", "extra") scope.addEventProcessor(eventProcessor()) scope.addAttachment(Attachment("path")) @@ -271,6 +281,7 @@ class ScopeTest { assertNull(scope.transaction) assertNull(scope.user) assertNull(scope.request) + assertNull(scope.screen) assertEquals(0, scope.fingerprint.size) assertEquals(0, scope.breadcrumbs.size) assertEquals(0, scope.tags.size) @@ -977,6 +988,14 @@ class ScopeTest { } } + @Test + fun `when setting the screen, it's stored in the app context as well`() { + val scope = Scope(SentryOptions()).apply { + screen = "MainActivity" + } + assertEquals(listOf("MainActivity"), scope.contexts.app!!.viewNames) + } + private fun eventProcessor(): EventProcessor { return object : EventProcessor { override fun process(event: SentryEvent, hint: Hint): SentryEvent? { From 73922110d8f9b928c9a8ab886da5ae1123397d4f Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 19 Oct 2023 11:26:01 +0200 Subject: [PATCH 3/8] Update Changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e195ad09e..8d90db6e72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Add current activity name to app context ([#2999](https://github.com/getsentry/sentry-java/pull/2999)) + ## 6.32.0 ### Features From 2201017bb0a983c828346bf373aea6153b680341 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 24 Oct 2023 09:52:33 +0200 Subject: [PATCH 4/8] Extend (de)serialization tests --- .../src/test/java/io/sentry/protocol/AppSerializationTest.kt | 1 + sentry/src/test/resources/json/app.json | 3 ++- sentry/src/test/resources/json/contexts.json | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry/src/test/java/io/sentry/protocol/AppSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/AppSerializationTest.kt index f07938d4c5..c39f93643a 100644 --- a/sentry/src/test/java/io/sentry/protocol/AppSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/AppSerializationTest.kt @@ -30,6 +30,7 @@ class AppSerializationTest { "CAMERA" to "granted" ) inForeground = true + viewNames = listOf("MainActivity", "SidebarActivity") } } private val fixture = Fixture() diff --git a/sentry/src/test/resources/json/app.json b/sentry/src/test/resources/json/app.json index 5294b48e15..e835258efa 100644 --- a/sentry/src/test/resources/json/app.json +++ b/sentry/src/test/resources/json/app.json @@ -11,5 +11,6 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] } diff --git a/sentry/src/test/resources/json/contexts.json b/sentry/src/test/resources/json/contexts.json index 404b7b9c9d..97f06e0be6 100644 --- a/sentry/src/test/resources/json/contexts.json +++ b/sentry/src/test/resources/json/contexts.json @@ -13,7 +13,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { From 6cd7c4233687cd782046d594634d8204137f77f4 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Fri, 27 Oct 2023 14:49:44 +0200 Subject: [PATCH 5/8] Mark screen API as internal --- sentry/src/main/java/io/sentry/Scope.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index a24e72611c..0806cf7736 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -269,6 +269,7 @@ public void setUser(final @Nullable User user) { * * @return the name of the screen */ + @ApiStatus.Internal public @Nullable String getScreen() { return screen; } @@ -278,6 +279,7 @@ public void setUser(final @Nullable User user) { * * @param screen the name of the screen */ + @ApiStatus.Internal public void setScreen(final @Nullable String screen) { this.screen = screen; From 11559df83f8eca88c70eb858c58a040561c83392 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Fri, 27 Oct 2023 14:49:50 +0200 Subject: [PATCH 6/8] Fix tests --- sentry/src/test/resources/json/sentry_base_event.json | 3 ++- .../test/resources/json/sentry_base_event_with_null_extra.json | 3 ++- sentry/src/test/resources/json/sentry_event.json | 3 ++- sentry/src/test/resources/json/sentry_transaction.json | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sentry/src/test/resources/json/sentry_base_event.json b/sentry/src/test/resources/json/sentry_base_event.json index 85459a9729..48b7c1ccb9 100644 --- a/sentry/src/test/resources/json/sentry_base_event.json +++ b/sentry/src/test/resources/json/sentry_base_event.json @@ -16,7 +16,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { diff --git a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json index bf364342b5..773eb99e2f 100644 --- a/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json +++ b/sentry/src/test/resources/json/sentry_base_event_with_null_extra.json @@ -16,7 +16,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { diff --git a/sentry/src/test/resources/json/sentry_event.json b/sentry/src/test/resources/json/sentry_event.json index e8ca503d08..5d57960db9 100644 --- a/sentry/src/test/resources/json/sentry_event.json +++ b/sentry/src/test/resources/json/sentry_event.json @@ -151,7 +151,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { diff --git a/sentry/src/test/resources/json/sentry_transaction.json b/sentry/src/test/resources/json/sentry_transaction.json index 4a849d3c02..5b0f0af221 100644 --- a/sentry/src/test/resources/json/sentry_transaction.json +++ b/sentry/src/test/resources/json/sentry_transaction.json @@ -60,7 +60,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { From 49a0e71b6a8d91dd06cabb7ea246fff38ff3e553 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Fri, 27 Oct 2023 17:14:21 +0200 Subject: [PATCH 7/8] Fix tests --- .../resources/json/sentry_transaction_legacy_date_format.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json index c667651b8f..679b3a624b 100644 --- a/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json +++ b/sentry/src/test/resources/json/sentry_transaction_legacy_date_format.json @@ -60,7 +60,8 @@ "WRITE_EXTERNAL_STORAGE": "not_granted", "CAMERA": "granted" }, - "in_foreground": true + "in_foreground": true, + "view_names": ["MainActivity", "SidebarActivity"] }, "browser": { From c44b4b757970d85bd517b5854ef748faad4ce726 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 8 Nov 2023 08:32:04 +0100 Subject: [PATCH 8/8] Ensure screen scope change is propagated --- sentry/src/main/java/io/sentry/Scope.java | 4 ++++ sentry/src/test/java/io/sentry/ScopeTest.kt | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Scope.java b/sentry/src/main/java/io/sentry/Scope.java index 0806cf7736..86ac486b46 100644 --- a/sentry/src/main/java/io/sentry/Scope.java +++ b/sentry/src/main/java/io/sentry/Scope.java @@ -297,6 +297,10 @@ public void setScreen(final @Nullable String screen) { viewNames.add(screen); app.setViewNames(viewNames); } + + for (final IScopeObserver observer : options.getScopeObservers()) { + observer.setContexts(contexts); + } } /** diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 22578e7a76..a7043d1208 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -6,6 +6,7 @@ import io.sentry.protocol.User import io.sentry.test.callMethod import org.junit.Assert.assertArrayEquals import org.mockito.kotlin.argThat +import org.mockito.kotlin.check import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.times @@ -996,6 +997,22 @@ class ScopeTest { assertEquals(listOf("MainActivity"), scope.contexts.app!!.viewNames) } + @Test + fun `when setting the screen, app context change is propagated`() { + val options = SentryOptions() + val observer = mock() + options.addScopeObserver(observer) + + Scope(options).apply { + screen = "MainActivity" + } + verify(observer).setContexts( + check { contexts -> + assertEquals("MainActivity", contexts.app?.viewNames?.first()) + } + ) + } + private fun eventProcessor(): EventProcessor { return object : EventProcessor { override fun process(event: SentryEvent, hint: Hint): SentryEvent? {