Skip to content

Commit 283d83e

Browse files
authored
Add current activity name to app context (getsentry#2999)
1 parent a3c77bc commit 283d83e

File tree

21 files changed

+256
-33
lines changed

21 files changed

+256
-33
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add current activity name to app context ([#2999](https://github.com/getsentry/sentry-java/pull/2999))
8+
39
## 6.33.1
410

511
### Fixes

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.sentry.SpanStatus;
2929
import io.sentry.TransactionContext;
3030
import io.sentry.TransactionOptions;
31+
import io.sentry.android.core.internal.util.ClassUtil;
3132
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
3233
import io.sentry.protocol.MeasurementValue;
3334
import io.sentry.protocol.TransactionNameSource;
@@ -372,6 +373,10 @@ public synchronized void onActivityCreated(
372373
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
373374
setColdStart(savedInstanceState);
374375
addBreadcrumb(activity, "created");
376+
if (hub != null) {
377+
final @Nullable String activityClassName = ClassUtil.getClassName(activity);
378+
hub.configureScope(scope -> scope.setScreen(activityClassName));
379+
}
375380
startTracing(activity);
376381
final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
377382

sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,19 @@ public static Map<String, Object> serializeScope(
9797
@Nullable App app = scope.getContexts().getApp();
9898
if (app == null) {
9999
app = new App();
100-
app.setAppName(ContextUtils.getApplicationName(context, options.getLogger()));
101-
app.setAppStartTime(DateUtils.toUtilDate(AppStartState.getInstance().getAppStartTime()));
102-
103-
final @NotNull BuildInfoProvider buildInfoProvider =
104-
new BuildInfoProvider(options.getLogger());
105-
final @Nullable PackageInfo packageInfo =
106-
ContextUtils.getPackageInfo(
107-
context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider);
108-
if (packageInfo != null) {
109-
ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app);
110-
}
111-
scope.getContexts().setApp(app);
112100
}
101+
app.setAppName(ContextUtils.getApplicationName(context, options.getLogger()));
102+
app.setAppStartTime(DateUtils.toUtilDate(AppStartState.getInstance().getAppStartTime()));
103+
104+
final @NotNull BuildInfoProvider buildInfoProvider =
105+
new BuildInfoProvider(options.getLogger());
106+
final @Nullable PackageInfo packageInfo =
107+
ContextUtils.getPackageInfo(
108+
context, PackageManager.GET_PERMISSIONS, options.getLogger(), buildInfoProvider);
109+
if (packageInfo != null) {
110+
ContextUtils.setAppPackageInfo(packageInfo, buildInfoProvider, app);
111+
}
112+
scope.getContexts().setApp(app);
113113

114114
writer.name("user").value(logger, scope.getUser());
115115
writer.name("contexts").value(logger, scope.getContexts());

sentry-android-core/src/main/java/io/sentry/android/core/ViewHierarchyEventProcessor.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.android.core.internal.gestures.ViewUtils;
1616
import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
1717
import io.sentry.android.core.internal.util.AndroidMainThreadChecker;
18+
import io.sentry.android.core.internal.util.ClassUtil;
1819
import io.sentry.android.core.internal.util.Debouncer;
1920
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter;
2021
import io.sentry.protocol.ViewHierarchy;
@@ -240,10 +241,7 @@ private static void addChildren(
240241
private static ViewHierarchyNode viewToNode(@NotNull final View view) {
241242
@NotNull final ViewHierarchyNode node = new ViewHierarchyNode();
242243

243-
@Nullable String className = view.getClass().getCanonicalName();
244-
if (className == null) {
245-
className = view.getClass().getSimpleName();
246-
}
244+
@Nullable String className = ClassUtil.getClassName(view);
247245
node.setType(className);
248246

249247
try {

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/AndroidViewGestureTargetLocator.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.widget.AbsListView;
66
import android.widget.ScrollView;
77
import androidx.core.view.ScrollingView;
8+
import io.sentry.android.core.internal.util.ClassUtil;
89
import io.sentry.internal.gestures.GestureTargetLocator;
910
import io.sentry.internal.gestures.UiElement;
1011
import org.jetbrains.annotations.ApiStatus;
@@ -44,10 +45,7 @@ && isViewScrollable(view, isAndroidXAvailable)) {
4445
private UiElement createUiElement(final @NotNull View targetView) {
4546
try {
4647
final String resourceName = ViewUtils.getResourceId(targetView);
47-
@Nullable String className = targetView.getClass().getCanonicalName();
48-
if (className == null) {
49-
className = targetView.getClass().getSimpleName();
50-
}
48+
@Nullable String className = ClassUtil.getClassName(targetView);
5149
return new UiElement(targetView, className, resourceName, null, ORIGIN);
5250
} catch (Resources.NotFoundException ignored) {
5351
return null;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.sentry.android.core.internal.util;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
import org.jetbrains.annotations.Nullable;
5+
6+
@ApiStatus.Internal
7+
public class ClassUtil {
8+
9+
public static @Nullable String getClassName(final @Nullable Object object) {
10+
if (object == null) {
11+
return null;
12+
}
13+
final @Nullable String canonicalName = object.getClass().getCanonicalName();
14+
if (canonicalName != null) {
15+
return canonicalName;
16+
}
17+
return object.getClass().getSimpleName();
18+
}
19+
}

sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,10 +1405,31 @@ class ActivityLifecycleIntegrationTest {
14051405
sut.register(fixture.hub, fixture.options)
14061406
sut.onActivityCreated(activity, fixture.bundle)
14071407

1408-
verify(fixture.hub).configureScope(any())
1408+
// once for the screen, and once for the tracing propagation context
1409+
verify(fixture.hub, times(2)).configureScope(any())
14091410
assertNotSame(propagationContextAtStart, scope.propagationContext)
14101411
}
14111412

1413+
@Test
1414+
fun `sets the activity as the current screen`() {
1415+
val sut = fixture.getSut()
1416+
val activity = mock<Activity>()
1417+
fixture.options.enableTracing = false
1418+
1419+
val argumentCaptor: ArgumentCaptor<ScopeCallback> = ArgumentCaptor.forClass(ScopeCallback::class.java)
1420+
val scope = mock<Scope>()
1421+
whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer {
1422+
argumentCaptor.value.run(scope)
1423+
}
1424+
1425+
sut.register(fixture.hub, fixture.options)
1426+
sut.onActivityCreated(activity, fixture.bundle)
1427+
1428+
// once for the screen, and once for the tracing propagation context
1429+
verify(fixture.hub, times(2)).configureScope(any())
1430+
verify(scope).setScreen(any())
1431+
}
1432+
14121433
@Test
14131434
fun `does not start another new trace if one has already been started but does after activity was destroyed`() {
14141435
val sut = fixture.getSut()
@@ -1425,21 +1446,25 @@ class ActivityLifecycleIntegrationTest {
14251446
sut.register(fixture.hub, fixture.options)
14261447
sut.onActivityCreated(activity, fixture.bundle)
14271448

1428-
verify(fixture.hub).configureScope(any())
1449+
// once for the screen, and once for the tracing propagation context
1450+
verify(fixture.hub, times(2)).configureScope(any())
1451+
14291452
val propagationContextAfterNewTrace = scope.propagationContext
14301453
assertNotSame(propagationContextAtStart, propagationContextAfterNewTrace)
14311454

14321455
clearInvocations(fixture.hub)
14331456
sut.onActivityCreated(activity, fixture.bundle)
14341457

1435-
verify(fixture.hub, never()).configureScope(any())
1458+
// once for the screen, but not for the tracing propagation context
1459+
verify(fixture.hub).configureScope(any())
14361460
assertSame(propagationContextAfterNewTrace, scope.propagationContext)
14371461

14381462
sut.onActivityDestroyed(activity)
14391463

14401464
clearInvocations(fixture.hub)
14411465
sut.onActivityCreated(activity, fixture.bundle)
1442-
verify(fixture.hub).configureScope(any())
1466+
// once for the screen, and once for the tracing propagation context
1467+
verify(fixture.hub, times(2)).configureScope(any())
14431468
assertNotSame(propagationContextAfterNewTrace, scope.propagationContext)
14441469
}
14451470

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.sentry.android.core.internal.util
2+
3+
import java.util.concurrent.Callable
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertNull
7+
import kotlin.test.assertTrue
8+
9+
class ClassUtilTest {
10+
11+
class Outer {
12+
class Inner {
13+
val x: Callable<Boolean> = Callable<Boolean> { false }
14+
}
15+
}
16+
17+
@Test
18+
fun `getClassName returns cannonical name by default`() {
19+
val name = ClassUtil.getClassName(Outer.Inner())
20+
assertEquals("io.sentry.android.core.internal.util.ClassUtilTest.Outer.Inner", name)
21+
}
22+
23+
@Test
24+
fun `getClassName falls back to simple name for anonymous classes`() {
25+
val name = ClassUtil.getClassName(Outer.Inner().x)
26+
assertTrue(name!!.contains("$"))
27+
}
28+
29+
@Test
30+
fun `getClassName returns null when obj is null`() {
31+
val name = ClassUtil.getClassName(null)
32+
assertNull(name)
33+
}
34+
}

sentry/api/sentry.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ public final class io/sentry/Scope {
13851385
public fun getLevel ()Lio/sentry/SentryLevel;
13861386
public fun getPropagationContext ()Lio/sentry/PropagationContext;
13871387
public fun getRequest ()Lio/sentry/protocol/Request;
1388+
public fun getScreen ()Ljava/lang/String;
13881389
public fun getSession ()Lio/sentry/Session;
13891390
public fun getSpan ()Lio/sentry/ISpan;
13901391
public fun getTags ()Ljava/util/Map;
@@ -1406,6 +1407,7 @@ public final class io/sentry/Scope {
14061407
public fun setLevel (Lio/sentry/SentryLevel;)V
14071408
public fun setPropagationContext (Lio/sentry/PropagationContext;)V
14081409
public fun setRequest (Lio/sentry/protocol/Request;)V
1410+
public fun setScreen (Ljava/lang/String;)V
14091411
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
14101412
public fun setTransaction (Lio/sentry/ITransaction;)V
14111413
public fun setTransaction (Ljava/lang/String;)V
@@ -3107,6 +3109,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
31073109
public fun getInForeground ()Ljava/lang/Boolean;
31083110
public fun getPermissions ()Ljava/util/Map;
31093111
public fun getUnknown ()Ljava/util/Map;
3112+
public fun getViewNames ()Ljava/util/List;
31103113
public fun hashCode ()I
31113114
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
31123115
public fun setAppBuild (Ljava/lang/String;)V
@@ -3119,6 +3122,7 @@ public final class io/sentry/protocol/App : io/sentry/JsonSerializable, io/sentr
31193122
public fun setInForeground (Ljava/lang/Boolean;)V
31203123
public fun setPermissions (Ljava/util/Map;)V
31213124
public fun setUnknown (Ljava/util/Map;)V
3125+
public fun setViewNames (Ljava/util/List;)V
31223126
}
31233127

31243128
public final class io/sentry/protocol/App$Deserializer : io/sentry/JsonDeserializer {
@@ -3137,6 +3141,7 @@ public final class io/sentry/protocol/App$JsonKeys {
31373141
public static final field BUILD_TYPE Ljava/lang/String;
31383142
public static final field DEVICE_APP_HASH Ljava/lang/String;
31393143
public static final field IN_FOREGROUND Ljava/lang/String;
3144+
public static final field VIEW_NAMES Ljava/lang/String;
31403145
public fun <init> ()V
31413146
}
31423147

sentry/src/main/java/io/sentry/Scope.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry;
22

3+
import io.sentry.protocol.App;
34
import io.sentry.protocol.Contexts;
45
import io.sentry.protocol.Request;
56
import io.sentry.protocol.TransactionNameSource;
@@ -33,6 +34,9 @@ public final class Scope {
3334
/** Scope's user */
3435
private @Nullable User user;
3536

37+
/** Scope's screen */
38+
private @Nullable String screen;
39+
3640
/** Scope's request */
3741
private @Nullable Request request;
3842

@@ -97,6 +101,7 @@ public Scope(final @NotNull Scope scope) {
97101

98102
final User userRef = scope.user;
99103
this.user = userRef != null ? new User(userRef) : null;
104+
this.screen = scope.screen;
100105

101106
final Request requestRef = scope.request;
102107
this.request = requestRef != null ? new Request(requestRef) : null;
@@ -259,6 +264,45 @@ public void setUser(final @Nullable User user) {
259264
}
260265
}
261266

267+
/**
268+
* Returns the Scope's current screen, previously set by {@link Scope#setScreen(String)}
269+
*
270+
* @return the name of the screen
271+
*/
272+
@ApiStatus.Internal
273+
public @Nullable String getScreen() {
274+
return screen;
275+
}
276+
277+
/**
278+
* Sets the Scope's current screen
279+
*
280+
* @param screen the name of the screen
281+
*/
282+
@ApiStatus.Internal
283+
public void setScreen(final @Nullable String screen) {
284+
this.screen = screen;
285+
286+
final @NotNull Contexts contexts = getContexts();
287+
@Nullable App app = contexts.getApp();
288+
if (app == null) {
289+
app = new App();
290+
contexts.setApp(app);
291+
}
292+
293+
if (screen == null) {
294+
app.setViewNames(null);
295+
} else {
296+
final @NotNull List<String> viewNames = new ArrayList<>(1);
297+
viewNames.add(screen);
298+
app.setViewNames(viewNames);
299+
}
300+
301+
for (final IScopeObserver observer : options.getScopeObservers()) {
302+
observer.setContexts(contexts);
303+
}
304+
}
305+
262306
/**
263307
* Returns the Scope's request
264308
*
@@ -426,6 +470,7 @@ public void clear() {
426470
level = null;
427471
user = null;
428472
request = null;
473+
screen = null;
429474
fingerprint.clear();
430475
clearBreadcrumbs();
431476
tags.clear();

0 commit comments

Comments
 (0)