Skip to content

Hook User Interaction integration into running Activity in case of deferred SDK init #4337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
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
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