Skip to content

Commit

Permalink
Add current activity name to app context (#2999)
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi committed Nov 8, 2023
1 parent a3c77bc commit 283d83e
Show file tree
Hide file tree
Showing 21 changed files with 256 additions and 33 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add current activity name to app context ([#2999](https://github.com/getsentry/sentry-java/pull/2999))

## 6.33.1

### Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,19 @@ public static Map<String, Object> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Activity>()
fixture.options.enableTracing = false

val argumentCaptor: ArgumentCaptor<ScopeCallback> = ArgumentCaptor.forClass(ScopeCallback::class.java)
val scope = mock<Scope>()
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()
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Boolean> = Callable<Boolean> { 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)
}
}
5 changes: 5 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -3107,6 +3109,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
Expand All @@ -3119,6 +3122,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 {
Expand All @@ -3137,6 +3141,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 <init> ()V
}

Expand Down
45 changes: 45 additions & 0 deletions sentry/src/main/java/io/sentry/Scope.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -259,6 +264,45 @@ 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
*/
@ApiStatus.Internal
public @Nullable String getScreen() {
return screen;
}

/**
* Sets the Scope's current screen
*
* @param screen the name of the screen
*/
@ApiStatus.Internal
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<String> viewNames = new ArrayList<>(1);
viewNames.add(screen);
app.setViewNames(viewNames);
}

for (final IScopeObserver observer : options.getScopeObservers()) {
observer.setContexts(contexts);
}
}

/**
* Returns the Scope's request
*
Expand Down Expand Up @@ -426,6 +470,7 @@ public void clear() {
level = null;
user = null;
request = null;
screen = null;
fingerprint.clear();
clearBreadcrumbs();
tags.clear();
Expand Down
Loading

0 comments on commit 283d83e

Please sign in to comment.