Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Hook User Interaction integration into running Activity in case of deferred SDK init ([#4337](https://github.com/getsentry/sentry-java/pull/4337))

## 8.11.1

### Fixes
Expand Down
17 changes: 4 additions & 13 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -198,24 +198,12 @@ public final class io/sentry/android/core/ContextUtils {

public class io/sentry/android/core/CurrentActivityHolder {
public fun clearActivity ()V
public fun clearActivity (Landroid/app/Activity;)V
public fun getActivity ()Landroid/app/Activity;
public static fun getInstance ()Lio/sentry/android/core/CurrentActivityHolder;
public fun setActivity (Landroid/app/Activity;)V
}

public final class io/sentry/android/core/CurrentActivityIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/app/Application;)V
public fun close ()V
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityDestroyed (Landroid/app/Activity;)V
public fun onActivityPaused (Landroid/app/Activity;)V
public fun onActivityResumed (Landroid/app/Activity;)V
public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityStarted (Landroid/app/Activity;)V
public fun onActivityStopped (Landroid/app/Activity;)V
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/DeviceInfoUtil {
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;)V
public fun collectDeviceInformation (ZZ)Lio/sentry/protocol/Device;
Expand Down Expand Up @@ -502,7 +490,10 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public fun isAppLaunchedInForeground ()Z
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityDestroyed (Landroid/app/Activity;)V
public fun onActivityPaused (Landroid/app/Activity;)V
public fun onActivityResumed (Landroid/app/Activity;)V
public fun onActivityStarted (Landroid/app/Activity;)V
public fun onActivityStopped (Landroid/app/Activity;)V
public fun onAppStartSpansSent ()V
public static fun onApplicationCreate (Landroid/app/Application;)V
public static fun onApplicationPostCreate (Landroid/app/Application;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,6 @@ static void installDefaultIntegrations(
new ActivityLifecycleIntegration(
(Application) context, buildInfoProvider, activityFramesTracker));
options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context));
options.addIntegration(new CurrentActivityIntegration((Application) context));
options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
if (isFragmentAvailable) {
options.addIntegration(new FragmentLifecycleIntegration((Application) context, true, true));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package io.sentry.android.core;

import android.app.Activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public class CurrentActivityHolder {
Expand All @@ -16,7 +15,7 @@ private CurrentActivityHolder() {}

private @Nullable WeakReference<Activity> currentActivity;

public static @NonNull CurrentActivityHolder getInstance() {
public static @NotNull CurrentActivityHolder getInstance() {
return instance;
}

Expand All @@ -27,7 +26,7 @@ private CurrentActivityHolder() {}
return null;
}

public void setActivity(final @NonNull Activity activity) {
public void setActivity(final @NotNull Activity activity) {
if (currentActivity != null && currentActivity.get() == activity) {
return;
}
Expand All @@ -38,4 +37,11 @@ public void setActivity(final @NonNull Activity activity) {
public void clearActivity() {
currentActivity = null;
}

public void clearActivity(final @NotNull Activity activity) {
if (currentActivity != null && currentActivity.get() != activity) {
return;
}
currentActivity = null;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import android.app.Application;
import android.os.Bundle;
import android.view.Window;
import androidx.lifecycle.Lifecycle;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actual access to these optional deps is guarded by an if-check, I guess that should be good enough. Will do a manual test as well, just to be sure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just did a test, works as expected 🚀

import androidx.lifecycle.LifecycleOwner;
import io.sentry.IScopes;
import io.sentry.Integration;
import io.sentry.SentryLevel;
Expand All @@ -27,12 +29,15 @@ public final class UserInteractionIntegration
private @Nullable SentryAndroidOptions options;

private final boolean isAndroidXAvailable;
private final boolean isAndroidxLifecycleAvailable;

public UserInteractionIntegration(
final @NotNull Application application, final @NotNull io.sentry.util.LoadClass classLoader) {
this.application = Objects.requireNonNull(application, "Application is required");
isAndroidXAvailable =
classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options);
isAndroidxLifecycleAvailable =
classLoader.isClassAvailable("androidx.lifecycle.Lifecycle", options);
}

private void startTracking(final @NotNull Activity activity) {
Expand Down Expand Up @@ -127,6 +132,17 @@ public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) {
application.registerActivityLifecycleCallbacks(this);
this.options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration installed.");
addIntegrationToSdkVersion("UserInteraction");

// In case of a deferred init, we hook into any resumed activity
if (isAndroidxLifecycleAvailable) {
final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity();
if (activity instanceof LifecycleOwner) {
if (((LifecycleOwner) activity).getLifecycle().getCurrentState()
== Lifecycle.State.RESUMED) {
startTracking(activity);
}
}
}
} else {
options
.getLogger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.sentry.TracesSamplingDecision;
import io.sentry.android.core.BuildInfoProvider;
import io.sentry.android.core.ContextUtils;
import io.sentry.android.core.CurrentActivityHolder;
import io.sentry.android.core.SentryAndroidOptions;
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
import io.sentry.util.AutoClosableReentrantLock;
Expand All @@ -42,7 +43,6 @@
*/
@ApiStatus.Internal
public class AppStartMetrics extends ActivityLifecycleCallbacksAdapter {

public enum AppStartType {
UNKNOWN,
COLD,
Expand Down Expand Up @@ -342,10 +342,12 @@ private void checkCreateTimeOnMain() {

@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
final long nowUptimeMs = SystemClock.uptimeMillis();
CurrentActivityHolder.getInstance().setActivity(activity);

// the first activity determines the app start type
if (activeActivitiesCounter.incrementAndGet() == 1 && !firstDrawDone.get()) {
final long nowUptimeMs = SystemClock.uptimeMillis();

// If the app (process) was launched more than 1 minute ago, it's likely wrong
final long durationSinceAppStartMillis = nowUptimeMs - appStartSpan.getStartUptimeMs();
if (!appLaunchedInForeground || durationSinceAppStartMillis > TimeUnit.MINUTES.toMillis(1)) {
Expand All @@ -367,6 +369,8 @@ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle saved

@Override
public void onActivityStarted(@NonNull Activity activity) {
CurrentActivityHolder.getInstance().setActivity(activity);

if (firstDrawDone.get()) {
return;
}
Expand All @@ -378,8 +382,25 @@ public void onActivityStarted(@NonNull Activity activity) {
}
}

@Override
public void onActivityResumed(@NonNull Activity activity) {
CurrentActivityHolder.getInstance().setActivity(activity);
}

@Override
public void onActivityPaused(@NonNull Activity activity) {
CurrentActivityHolder.getInstance().clearActivity(activity);
}

@Override
public void onActivityStopped(@NonNull Activity activity) {
CurrentActivityHolder.getInstance().clearActivity(activity);
}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {
CurrentActivityHolder.getInstance().clearActivity(activity);

final int remainingActivities = activeActivitiesCounter.decrementAndGet();
// if the app is moving into background
// as the next Activity is considered like a new app start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,15 +634,6 @@ class AndroidOptionsInitializerTest {
assertTrue { fixture.sentryOptions.envelopeDiskCache is AndroidEnvelopeCache }
}

@Test
fun `CurrentActivityIntegration is added by default`() {
fixture.initSut(useRealContext = true)

val actual =
fixture.sentryOptions.integrations.firstOrNull { it is CurrentActivityIntegration }
assertNotNull(actual)
}

@Test
fun `When Activity Frames Tracking is enabled, the Activity Frames Tracker should be unavailable`() {
fixture.initSut(
Expand Down
Loading
Loading