@@ -206,7 +206,7 @@ public interface BeforeCaptureCallback {
*/
private boolean attachAnrThreadDump = false;
- private boolean enablePerformanceV2 = false;
+ private boolean enablePerformanceV2 = true;
private @Nullable SentryFrameMetricsCollector frameMetricsCollector;
@@ -337,27 +337,6 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
setEnableUserInteractionBreadcrumbs(enable);
}
- /**
- * Returns the interval for profiling traces in milliseconds.
- *
- * @return the interval for profiling traces in milliseconds.
- * @deprecated has no effect and will be removed in future versions. It now just returns 0.
- */
- @Deprecated
- @SuppressWarnings("InlineMeSuggester")
- public int getProfilingTracesIntervalMillis() {
- return 0;
- }
-
- /**
- * Sets the interval for profiling traces in milliseconds.
- *
- * @param profilingTracesIntervalMillis - the interval for profiling traces in milliseconds.
- * @deprecated has no effect and will be removed in future versions.
- */
- @Deprecated
- public void setProfilingTracesIntervalMillis(final int profilingTracesIntervalMillis) {}
-
/**
* Returns the Debug image loader
*
@@ -576,20 +555,18 @@ public void setAttachAnrThreadDump(final boolean attachAnrThreadDump) {
* @return true if performance-v2 is enabled. See {@link #setEnablePerformanceV2(boolean)} for
* more details.
*/
- @ApiStatus.Experimental
public boolean isEnablePerformanceV2() {
return enablePerformanceV2;
}
/**
- * Experimental: Enables or disables the Performance V2 SDK features.
+ * Enables or disables the Performance V2 SDK features.
*
* With this change - Cold app start spans will provide more accurate timings - Cold app start
* spans will be enriched with detailed ContentProvider, Application and Activity startup times
*
* @param enablePerformanceV2 true if enabled or false otherwise
*/
- @ApiStatus.Experimental
public void setEnablePerformanceV2(final boolean enablePerformanceV2) {
this.enablePerformanceV2 = enablePerformanceV2;
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java
index 354448c4f2..7334b0bb9e 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java
@@ -14,6 +14,7 @@
import android.os.SystemClock;
import androidx.annotation.NonNull;
import io.sentry.ILogger;
+import io.sentry.ISentryLifecycleToken;
import io.sentry.ITransactionProfiler;
import io.sentry.JsonSerializer;
import io.sentry.NoOpLogger;
@@ -28,6 +29,7 @@
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
+import io.sentry.util.AutoClosableReentrantLock;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -53,6 +55,7 @@ public final class SentryPerformanceProvider extends EmptySecureContentProvider
private final @NotNull ILogger logger;
private final @NotNull BuildInfoProvider buildInfoProvider;
+ private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
@TestOnly
SentryPerformanceProvider(
@@ -92,7 +95,7 @@ public String getType(@NotNull Uri uri) {
@Override
public void shutdown() {
- synchronized (AppStartMetrics.getInstance()) {
+ try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) {
final @Nullable ITransactionProfiler appStartProfiler =
AppStartMetrics.getInstance().getAppStartProfiler();
if (appStartProfiler != null) {
@@ -159,10 +162,9 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
final @NotNull ITransactionProfiler appStartProfiler =
new AndroidTransactionProfiler(
- context.getApplicationContext(),
+ context,
buildInfoProvider,
- new SentryFrameMetricsCollector(
- context.getApplicationContext(), logger, buildInfoProvider),
+ new SentryFrameMetricsCollector(context, logger, buildInfoProvider),
logger,
profilingOptions.getProfilingTracesDirPath(),
profilingOptions.isProfilingEnabled(),
@@ -201,6 +203,7 @@ private void onAppLaunched(
final @NotNull TimeSpan appStartTimespan = appStartMetrics.getAppStartTimeSpan();
appStartTimespan.setStartedAt(Process.getStartUptimeMillis());
+ appStartMetrics.registerApplicationForegroundCheck(app);
final AtomicBoolean firstDrawDone = new AtomicBoolean(false);
@@ -301,14 +304,16 @@ public void onActivityDestroyed(@NonNull Activity activity) {
}
@TestOnly
- synchronized void onAppStartDone() {
- final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
- appStartMetrics.getSdkInitTimeSpan().stop();
- appStartMetrics.getAppStartTimeSpan().stop();
-
- if (app != null) {
- if (activityCallback != null) {
- app.unregisterActivityLifecycleCallbacks(activityCallback);
+ void onAppStartDone() {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
+ final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
+ appStartMetrics.getSdkInitTimeSpan().stop();
+ appStartMetrics.getAppStartTimeSpan().stop();
+
+ if (app != null) {
+ if (activityCallback != null) {
+ app.unregisterActivityLifecycleCallbacks(activityCallback);
+ }
}
}
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java
index 5535bccb91..d4e47ddc80 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java
@@ -2,6 +2,7 @@
import io.sentry.DateUtils;
import io.sentry.IPerformanceContinuousCollector;
+import io.sentry.ISentryLifecycleToken;
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.NoOpSpan;
@@ -11,6 +12,7 @@
import io.sentry.SpanDataConvention;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.protocol.MeasurementValue;
+import io.sentry.util.AutoClosableReentrantLock;
import java.util.Date;
import java.util.Iterator;
import java.util.SortedSet;
@@ -34,7 +36,7 @@ public class SpanFrameMetricsCollector
private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(new Date(0), 0);
private final boolean enabled;
- private final @NotNull Object lock = new Object();
+ protected final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
private final @NotNull SentryFrameMetricsCollector frameMetricsCollector;
private volatile @Nullable String listenerId;
@@ -43,17 +45,19 @@ public class SpanFrameMetricsCollector
private final @NotNull SortedSet runningSpans =
new TreeSet<>(
(o1, o2) -> {
+ if (o1 == o2) {
+ return 0;
+ }
int timeDiff = o1.getStartDate().compareTo(o2.getStartDate());
if (timeDiff != 0) {
return timeDiff;
- } else {
- // TreeSet uses compareTo to check for duplicates, so ensure that
- // two non-equal spans with the same start date are not considered equal
- return o1.getSpanContext()
- .getSpanId()
- .toString()
- .compareTo(o2.getSpanContext().getSpanId().toString());
}
+ // TreeSet uses compareTo to check for duplicates, so ensure that
+ // two non-equal spans with the same start date are not considered equal
+ return o1.getSpanContext()
+ .getSpanId()
+ .toString()
+ .compareTo(o2.getSpanContext().getSpanId().toString());
});
// all collected frames, sorted by frame end time
@@ -85,7 +89,7 @@ public void onSpanStarted(final @NotNull ISpan span) {
return;
}
- synchronized (lock) {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
runningSpans.add(span);
if (listenerId == null) {
@@ -109,7 +113,7 @@ public void onSpanFinished(final @NotNull ISpan span) {
}
// ignore span if onSpanStarted was never called for it
- synchronized (lock) {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
if (!runningSpans.contains(span)) {
return;
}
@@ -117,7 +121,7 @@ public void onSpanFinished(final @NotNull ISpan span) {
captureFrameMetrics(span);
- synchronized (lock) {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
if (runningSpans.isEmpty()) {
clear();
} else {
@@ -130,7 +134,7 @@ public void onSpanFinished(final @NotNull ISpan span) {
private void captureFrameMetrics(@NotNull final ISpan span) {
// TODO lock still required?
- synchronized (lock) {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
boolean removed = runningSpans.remove(span);
if (!removed) {
return;
@@ -224,7 +228,7 @@ private void captureFrameMetrics(@NotNull final ISpan span) {
@Override
public void clear() {
- synchronized (lock) {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
if (listenerId != null) {
frameMetricsCollector.stopCollection(listenerId);
listenerId = null;
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java
index 1c22a7dcc8..00e0dde646 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java
@@ -6,6 +6,7 @@
import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE;
import static android.content.Intent.ACTION_AIRPLANE_MODE_CHANGED;
import static android.content.Intent.ACTION_APP_ERROR;
+import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_BATTERY_LOW;
import static android.content.Intent.ACTION_BATTERY_OKAY;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
@@ -40,11 +41,14 @@
import android.os.Bundle;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
-import io.sentry.IHub;
-import io.sentry.ILogger;
+import io.sentry.IScopes;
+import io.sentry.ISentryLifecycleToken;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
+import io.sentry.android.core.internal.util.AndroidCurrentDateProvider;
+import io.sentry.android.core.internal.util.Debouncer;
+import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.Objects;
import io.sentry.util.StringUtils;
import java.io.Closeable;
@@ -67,7 +71,7 @@ public final class SystemEventsBreadcrumbsIntegration implements Integration, Cl
private final @NotNull List actions;
private boolean isClosed = false;
- private final @NotNull Object startLock = new Object();
+ private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock();
public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) {
this(context, getDefaultActions());
@@ -75,13 +79,14 @@ public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) {
public SystemEventsBreadcrumbsIntegration(
final @NotNull Context context, final @NotNull List actions) {
- this.context = Objects.requireNonNull(context, "Context is required");
+ this.context =
+ Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
this.actions = Objects.requireNonNull(actions, "Actions list is required");
}
@Override
- public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
- Objects.requireNonNull(hub, "Hub is required");
+ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
+ Objects.requireNonNull(scopes, "Scopes are required");
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
@@ -101,9 +106,9 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
.getExecutorService()
.submit(
() -> {
- synchronized (startLock) {
+ try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) {
if (!isClosed) {
- startSystemEventsReceiver(hub, (SentryAndroidOptions) options);
+ startSystemEventsReceiver(scopes, (SentryAndroidOptions) options);
}
}
});
@@ -119,8 +124,8 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
}
private void startSystemEventsReceiver(
- final @NotNull IHub hub, final @NotNull SentryAndroidOptions options) {
- receiver = new SystemEventsBroadcastReceiver(hub, options.getLogger());
+ final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) {
+ receiver = new SystemEventsBroadcastReceiver(scopes, options);
final IntentFilter filter = new IntentFilter();
for (String item : actions) {
filter.addAction(item);
@@ -154,6 +159,7 @@ private void startSystemEventsReceiver(
actions.add(ACTION_AIRPLANE_MODE_CHANGED);
actions.add(ACTION_BATTERY_LOW);
actions.add(ACTION_BATTERY_OKAY);
+ actions.add(ACTION_BATTERY_CHANGED);
actions.add(ACTION_BOOT_COMPLETED);
actions.add(ACTION_CAMERA_BUTTON);
actions.add(ACTION_CONFIGURATION_CHANGED);
@@ -189,7 +195,7 @@ private void startSystemEventsReceiver(
@Override
public void close() throws IOException {
- synchronized (startLock) {
+ try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) {
isClosed = true;
}
if (receiver != null) {
@@ -204,52 +210,96 @@ public void close() throws IOException {
static final class SystemEventsBroadcastReceiver extends BroadcastReceiver {
- private final @NotNull IHub hub;
- private final @NotNull ILogger logger;
+ private static final long DEBOUNCE_WAIT_TIME_MS = 60 * 1000;
+ private final @NotNull IScopes scopes;
+ private final @NotNull SentryAndroidOptions options;
+ private final @NotNull Debouncer batteryChangedDebouncer =
+ new Debouncer(AndroidCurrentDateProvider.getInstance(), DEBOUNCE_WAIT_TIME_MS, 0);
- SystemEventsBroadcastReceiver(final @NotNull IHub hub, final @NotNull ILogger logger) {
- this.hub = hub;
- this.logger = logger;
+ SystemEventsBroadcastReceiver(
+ final @NotNull IScopes scopes, final @NotNull SentryAndroidOptions options) {
+ this.scopes = scopes;
+ this.options = options;
}
@Override
- public void onReceive(Context context, Intent intent) {
- final Breadcrumb breadcrumb = new Breadcrumb();
+ public void onReceive(final Context context, final @NotNull Intent intent) {
+ final @Nullable String action = intent.getAction();
+ final boolean isBatteryChanged = ACTION_BATTERY_CHANGED.equals(action);
+
+ // aligning with iOS which only captures battery status changes every minute at maximum
+ if (isBatteryChanged && batteryChangedDebouncer.checkForDebounce()) {
+ return;
+ }
+
+ final long now = System.currentTimeMillis();
+ try {
+ options
+ .getExecutorService()
+ .submit(
+ () -> {
+ final Breadcrumb breadcrumb =
+ createBreadcrumb(now, intent, action, isBatteryChanged);
+ final Hint hint = new Hint();
+ hint.set(ANDROID_INTENT, intent);
+ scopes.addBreadcrumb(breadcrumb, hint);
+ });
+ } catch (Throwable t) {
+ options
+ .getLogger()
+ .log(SentryLevel.ERROR, t, "Failed to submit system event breadcrumb action.");
+ }
+ }
+
+ private @NotNull Breadcrumb createBreadcrumb(
+ final long timeMs,
+ final @NotNull Intent intent,
+ final @Nullable String action,
+ boolean isBatteryChanged) {
+ final Breadcrumb breadcrumb = new Breadcrumb(timeMs);
breadcrumb.setType("system");
breadcrumb.setCategory("device.event");
- final String action = intent.getAction();
- String shortAction = StringUtils.getStringAfterDot(action);
+ final String shortAction = StringUtils.getStringAfterDot(action);
if (shortAction != null) {
breadcrumb.setData("action", shortAction);
}
- final Bundle extras = intent.getExtras();
- final Map newExtras = new HashMap<>();
- if (extras != null && !extras.isEmpty()) {
- for (String item : extras.keySet()) {
- try {
- @SuppressWarnings("deprecation")
- Object value = extras.get(item);
- if (value != null) {
- newExtras.put(item, value.toString());
+ if (isBatteryChanged) {
+ final Float batteryLevel = DeviceInfoUtil.getBatteryLevel(intent, options);
+ if (batteryLevel != null) {
+ breadcrumb.setData("level", batteryLevel);
+ }
+ final Boolean isCharging = DeviceInfoUtil.isCharging(intent, options);
+ if (isCharging != null) {
+ breadcrumb.setData("charging", isCharging);
+ }
+ } else {
+ final Bundle extras = intent.getExtras();
+ final Map newExtras = new HashMap<>();
+ if (extras != null && !extras.isEmpty()) {
+ for (String item : extras.keySet()) {
+ try {
+ @SuppressWarnings("deprecation")
+ Object value = extras.get(item);
+ if (value != null) {
+ newExtras.put(item, value.toString());
+ }
+ } catch (Throwable exception) {
+ options
+ .getLogger()
+ .log(
+ SentryLevel.ERROR,
+ exception,
+ "%s key of the %s action threw an error.",
+ item,
+ action);
}
- } catch (Throwable exception) {
- logger.log(
- SentryLevel.ERROR,
- exception,
- "%s key of the %s action threw an error.",
- item,
- action);
}
+ breadcrumb.setData("extras", newExtras);
}
- breadcrumb.setData("extras", newExtras);
}
breadcrumb.setLevel(SentryLevel.INFO);
-
- final Hint hint = new Hint();
- hint.set(ANDROID_INTENT, intent);
-
- hub.addBreadcrumb(breadcrumb, hint);
+ return breadcrumb;
}
}
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java
index eaf5c64991..f835b3670b 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/TempSensorBreadcrumbsIntegration.java
@@ -11,10 +11,12 @@
import android.hardware.SensorManager;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
-import io.sentry.IHub;
+import io.sentry.IScopes;
+import io.sentry.ISentryLifecycleToken;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
+import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.Objects;
import java.io.Closeable;
import java.io.IOException;
@@ -26,20 +28,21 @@ public final class TempSensorBreadcrumbsIntegration
implements Integration, Closeable, SensorEventListener {
private final @NotNull Context context;
- private @Nullable IHub hub;
+ private @Nullable IScopes scopes;
private @Nullable SentryAndroidOptions options;
@TestOnly @Nullable SensorManager sensorManager;
private boolean isClosed = false;
- private final @NotNull Object startLock = new Object();
+ private final @NotNull AutoClosableReentrantLock startLock = new AutoClosableReentrantLock();
public TempSensorBreadcrumbsIntegration(final @NotNull Context context) {
- this.context = Objects.requireNonNull(context, "Context is required");
+ this.context =
+ Objects.requireNonNull(ContextUtils.getApplicationContext(context), "Context is required");
}
@Override
- public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
- this.hub = Objects.requireNonNull(hub, "Hub is required");
+ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
+ this.scopes = Objects.requireNonNull(scopes, "Scopes are required");
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
@@ -59,7 +62,7 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
.getExecutorService()
.submit(
() -> {
- synchronized (startLock) {
+ try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) {
if (!isClosed) {
startSensorListener(options);
}
@@ -100,7 +103,7 @@ private void startSensorListener(final @NotNull SentryOptions options) {
@Override
public void close() throws IOException {
- synchronized (startLock) {
+ try (final @NotNull ISentryLifecycleToken ignored = startLock.acquire()) {
isClosed = true;
}
if (sensorManager != null) {
@@ -121,7 +124,7 @@ public void onSensorChanged(final @NotNull SensorEvent event) {
return;
}
- if (hub != null) {
+ if (scopes != null) {
final Breadcrumb breadcrumb = new Breadcrumb();
breadcrumb.setType("system");
breadcrumb.setCategory("device.event");
@@ -134,7 +137,7 @@ public void onSensorChanged(final @NotNull SensorEvent event) {
final Hint hint = new Hint();
hint.set(ANDROID_SENSOR_EVENT, event);
- hub.addBreadcrumb(breadcrumb, hint);
+ scopes.addBreadcrumb(breadcrumb, hint);
}
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java
index c361529671..02a707173a 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/UserInteractionIntegration.java
@@ -6,7 +6,7 @@
import android.app.Application;
import android.os.Bundle;
import android.view.Window;
-import io.sentry.IHub;
+import io.sentry.IScopes;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
@@ -23,13 +23,13 @@ public final class UserInteractionIntegration
implements Integration, Closeable, Application.ActivityLifecycleCallbacks {
private final @NotNull Application application;
- private @Nullable IHub hub;
+ private @Nullable IScopes scopes;
private @Nullable SentryAndroidOptions options;
private final boolean isAndroidXAvailable;
public UserInteractionIntegration(
- final @NotNull Application application, final @NotNull LoadClass classLoader) {
+ 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);
@@ -44,14 +44,14 @@ private void startTracking(final @NotNull Activity activity) {
return;
}
- if (hub != null && options != null) {
+ if (scopes != null && options != null) {
Window.Callback delegate = window.getCallback();
if (delegate == null) {
delegate = new NoOpWindowCallback();
}
final SentryGestureListener gestureListener =
- new SentryGestureListener(activity, hub, options);
+ new SentryGestureListener(activity, scopes, options);
window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options));
}
}
@@ -102,13 +102,13 @@ public void onActivitySaveInstanceState(@NotNull Activity activity, @NotNull Bun
public void onActivityDestroyed(@NotNull Activity activity) {}
@Override
- public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
+ public void register(@NotNull IScopes scopes, @NotNull SentryOptions options) {
this.options =
Objects.requireNonNull(
(options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
"SentryAndroidOptions is required");
- this.hub = Objects.requireNonNull(hub, "Hub is required");
+ this.scopes = Objects.requireNonNull(scopes, "Scopes are required");
final boolean integrationEnabled =
this.options.isEnableUserInteractionBreadcrumbs()
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 30e9f8de11..8b6a1f87aa 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,7 +15,7 @@
import io.sentry.SentryLevel;
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.AndroidThreadChecker;
import io.sentry.android.core.internal.util.ClassUtil;
import io.sentry.android.core.internal.util.Debouncer;
import io.sentry.internal.viewhierarchy.ViewHierarchyExporter;
@@ -25,7 +25,7 @@
import io.sentry.util.HintUtils;
import io.sentry.util.JsonSerializationUtils;
import io.sentry.util.Objects;
-import io.sentry.util.thread.IMainThreadChecker;
+import io.sentry.util.thread.IThreadChecker;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -101,7 +101,7 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options)
snapshotViewHierarchy(
activity,
options.getViewHierarchyExporters(),
- options.getMainThreadChecker(),
+ options.getThreadChecker(),
options.getLogger());
if (viewHierarchy != null) {
@@ -113,13 +113,13 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options)
public static byte[] snapshotViewHierarchyAsData(
@Nullable Activity activity,
- @NotNull IMainThreadChecker mainThreadChecker,
+ @NotNull IThreadChecker threadChecker,
@NotNull ISerializer serializer,
@NotNull ILogger logger) {
@Nullable
ViewHierarchy viewHierarchy =
- snapshotViewHierarchy(activity, new ArrayList<>(0), mainThreadChecker, logger);
+ snapshotViewHierarchy(activity, new ArrayList<>(0), threadChecker, logger);
if (viewHierarchy == null) {
logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy.");
@@ -144,14 +144,14 @@ public static byte[] snapshotViewHierarchyAsData(
public static ViewHierarchy snapshotViewHierarchy(
final @Nullable Activity activity, final @NotNull ILogger logger) {
return snapshotViewHierarchy(
- activity, new ArrayList<>(0), AndroidMainThreadChecker.getInstance(), logger);
+ activity, new ArrayList<>(0), AndroidThreadChecker.getInstance(), logger);
}
@Nullable
public static ViewHierarchy snapshotViewHierarchy(
final @Nullable Activity activity,
final @NotNull List exporters,
- final @NotNull IMainThreadChecker mainThreadChecker,
+ final @NotNull IThreadChecker threadChecker,
final @NotNull ILogger logger) {
if (activity == null) {
@@ -172,7 +172,7 @@ public static ViewHierarchy snapshotViewHierarchy(
}
try {
- if (mainThreadChecker.isMainThread()) {
+ if (threadChecker.isMainThread()) {
return snapshotViewHierarchy(decorView, exporters);
} else {
final CountDownLatch latch = new CountDownLatch(1);
@@ -284,4 +284,9 @@ private static ViewHierarchyNode viewToNode(@NotNull final View view) {
return node;
}
+
+ @Override
+ public @Nullable Long getOrder() {
+ return 11000L;
+ }
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java
index 6c5bed5ae2..568b67f0b0 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/debugmeta/AssetsDebugMetaLoader.java
@@ -6,6 +6,7 @@
import android.content.res.AssetManager;
import io.sentry.ILogger;
import io.sentry.SentryLevel;
+import io.sentry.android.core.ContextUtils;
import io.sentry.internal.debugmeta.IDebugMetaLoader;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
@@ -24,7 +25,7 @@ public final class AssetsDebugMetaLoader implements IDebugMetaLoader {
private final @NotNull ILogger logger;
public AssetsDebugMetaLoader(final @NotNull Context context, final @NotNull ILogger logger) {
- this.context = context;
+ this.context = ContextUtils.getApplicationContext(context);
this.logger = logger;
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java
index 0ec0d83258..cd80f5ced7 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java
@@ -10,8 +10,8 @@
import android.view.Window;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
-import io.sentry.IHub;
import io.sentry.IScope;
+import io.sentry.IScopes;
import io.sentry.ITransaction;
import io.sentry.SentryLevel;
import io.sentry.SpanStatus;
@@ -43,7 +43,7 @@ private enum GestureType {
private static final String TRACE_ORIGIN = "auto.ui.gesture_listener";
private final @NotNull WeakReference activityRef;
- private final @NotNull IHub hub;
+ private final @NotNull IScopes scopes;
private final @NotNull SentryAndroidOptions options;
private @Nullable UiElement activeUiElement = null;
@@ -54,10 +54,10 @@ private enum GestureType {
public SentryGestureListener(
final @NotNull Activity currentActivity,
- final @NotNull IHub hub,
+ final @NotNull IScopes scopes,
final @NotNull SentryAndroidOptions options) {
this.activityRef = new WeakReference<>(currentActivity);
- this.hub = hub;
+ this.scopes = scopes;
this.options = options;
}
@@ -185,7 +185,7 @@ private void addBreadcrumb(
hint.set(ANDROID_MOTION_EVENT, motionEvent);
hint.set(ANDROID_VIEW, target.getView());
- hub.addBreadcrumb(
+ scopes.addBreadcrumb(
Breadcrumb.userInteraction(
type, target.getResourceName(), target.getClassName(), target.getTag(), additionalData),
hint);
@@ -202,7 +202,7 @@ private void startTracing(final @NotNull UiElement target, final @NotNull Gestur
if (!(options.isTracingEnabled() && options.isEnableUserInteractionTracing())) {
if (isNewInteraction) {
- TracingUtils.startNewTrace(hub);
+ TracingUtils.startNewTrace(scopes);
activeUiElement = target;
activeEventType = eventType;
}
@@ -251,14 +251,13 @@ private void startTracing(final @NotNull UiElement target, final @NotNull Gestur
TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION);
transactionOptions.setIdleTimeout(options.getIdleTimeout());
transactionOptions.setTrimEnd(true);
+ transactionOptions.setOrigin(TRACE_ORIGIN + "." + target.getOrigin());
final ITransaction transaction =
- hub.startTransaction(
+ scopes.startTransaction(
new TransactionContext(name, TransactionNameSource.COMPONENT, op), transactionOptions);
- transaction.getSpanContext().setOrigin(TRACE_ORIGIN + "." + target.getOrigin());
-
- hub.configureScope(
+ scopes.configureScope(
scope -> {
applyScope(scope, transaction);
});
@@ -278,7 +277,7 @@ void stopTracing(final @NotNull SpanStatus status) {
activeTransaction.finish();
}
}
- hub.configureScope(
+ scopes.configureScope(
scope -> {
// avoid method refs on Android due to some issues with older AGP setups
// noinspection Convert2MethodRef
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java
index 6d6f3737cb..b6374a32e3 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/modules/AssetsModulesLoader.java
@@ -3,6 +3,7 @@
import android.content.Context;
import io.sentry.ILogger;
import io.sentry.SentryLevel;
+import io.sentry.android.core.ContextUtils;
import io.sentry.internal.modules.ModulesLoader;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -19,7 +20,7 @@ public final class AssetsModulesLoader extends ModulesLoader {
public AssetsModulesLoader(final @NotNull Context context, final @NotNull ILogger logger) {
super(logger);
- this.context = context;
+ this.context = ContextUtils.getApplicationContext(context);
}
@Override
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java
index b8279edcb1..0afd2bce97 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidConnectionStatusProvider.java
@@ -12,6 +12,7 @@
import io.sentry.ILogger;
import io.sentry.SentryLevel;
import io.sentry.android.core.BuildInfoProvider;
+import io.sentry.android.core.ContextUtils;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
@@ -37,7 +38,7 @@ public AndroidConnectionStatusProvider(
@NotNull Context context,
@NotNull ILogger logger,
@NotNull BuildInfoProvider buildInfoProvider) {
- this.context = context;
+ this.context = ContextUtils.getApplicationContext(context);
this.logger = logger;
this.buildInfoProvider = buildInfoProvider;
this.registeredCallbacks = new HashMap<>();
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidMainThreadChecker.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java
similarity index 56%
rename from sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidMainThreadChecker.java
rename to sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java
index aa54790c47..15781d711f 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidMainThreadChecker.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/AndroidThreadChecker.java
@@ -1,22 +1,28 @@
package io.sentry.android.core.internal.util;
+import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import io.sentry.protocol.SentryThread;
-import io.sentry.util.thread.IMainThreadChecker;
+import io.sentry.util.thread.IThreadChecker;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/** Class that checks if a given thread is the Android Main/UI thread */
@ApiStatus.Internal
-public final class AndroidMainThreadChecker implements IMainThreadChecker {
+public final class AndroidThreadChecker implements IThreadChecker {
- private static final AndroidMainThreadChecker instance = new AndroidMainThreadChecker();
+ private static final AndroidThreadChecker instance = new AndroidThreadChecker();
+ public static volatile long mainThreadSystemId = Process.myTid();
- public static AndroidMainThreadChecker getInstance() {
+ public static AndroidThreadChecker getInstance() {
return instance;
}
- private AndroidMainThreadChecker() {}
+ private AndroidThreadChecker() {
+ // The first time this class is loaded, we make sure to set the correct mainThreadId
+ new Handler(Looper.getMainLooper()).post(() -> mainThreadSystemId = Process.myTid());
+ }
@Override
public boolean isMainThread(final long threadId) {
@@ -38,4 +44,9 @@ public boolean isMainThread(final @NotNull SentryThread sentryThread) {
final Long threadId = sentryThread.getId();
return threadId != null && isMainThread(threadId);
}
+
+ @Override
+ public long currentThreadSystemId() {
+ return Process.myTid();
+ }
}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java
index 8dcb994fbc..019db99fc7 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/CpuInfoUtils.java
@@ -1,5 +1,7 @@
package io.sentry.android.core.internal.util;
+import io.sentry.ISentryLifecycleToken;
+import io.sentry.util.AutoClosableReentrantLock;
import io.sentry.util.FileUtils;
import java.io.File;
import java.io.IOException;
@@ -14,6 +16,7 @@
public final class CpuInfoUtils {
private static final CpuInfoUtils instance = new CpuInfoUtils();
+ private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
public static CpuInfoUtils getInstance() {
return instance;
@@ -34,34 +37,36 @@ private CpuInfoUtils() {}
*
* @return A list with the frequency of each core of the cpu in Mhz
*/
- public synchronized @NotNull List readMaxFrequencies() {
- if (!cpuMaxFrequenciesMhz.isEmpty()) {
- return cpuMaxFrequenciesMhz;
- }
- File[] cpuDirs = new File(getSystemCpuPath()).listFiles();
- if (cpuDirs == null) {
- return new ArrayList<>();
- }
+ public @NotNull List readMaxFrequencies() {
+ try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
+ if (!cpuMaxFrequenciesMhz.isEmpty()) {
+ return cpuMaxFrequenciesMhz;
+ }
+ File[] cpuDirs = new File(getSystemCpuPath()).listFiles();
+ if (cpuDirs == null) {
+ return new ArrayList<>();
+ }
- for (File cpuDir : cpuDirs) {
- if (!cpuDir.getName().matches("cpu[0-9]+")) continue;
- File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH);
+ for (File cpuDir : cpuDirs) {
+ if (!cpuDir.getName().matches("cpu[0-9]+")) continue;
+ File cpuMaxFreqFile = new File(cpuDir, CPUINFO_MAX_FREQ_PATH);
- if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue;
+ if (!cpuMaxFreqFile.exists() || !cpuMaxFreqFile.canRead()) continue;
- long khz;
- try {
- String content = FileUtils.readText(cpuMaxFreqFile);
- if (content == null) continue;
- khz = Long.parseLong(content.trim());
- } catch (NumberFormatException e) {
- continue;
- } catch (IOException e) {
- continue;
+ long khz;
+ try {
+ String content = FileUtils.readText(cpuMaxFreqFile);
+ if (content == null) continue;
+ khz = Long.parseLong(content.trim());
+ } catch (NumberFormatException e) {
+ continue;
+ } catch (IOException e) {
+ continue;
+ }
+ cpuMaxFrequenciesMhz.add((int) (khz / 1000));
}
- cpuMaxFrequenciesMhz.add((int) (khz / 1000));
+ return cpuMaxFrequenciesMhz;
}
- return cpuMaxFrequenciesMhz;
}
@VisibleForTesting
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java
index 45e9d56877..d6cd7bc6af 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/ScreenshotUtils.java
@@ -14,7 +14,7 @@
import io.sentry.ILogger;
import io.sentry.SentryLevel;
import io.sentry.android.core.BuildInfoProvider;
-import io.sentry.util.thread.IMainThreadChecker;
+import io.sentry.util.thread.IThreadChecker;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -31,14 +31,13 @@ public class ScreenshotUtils {
final @NotNull Activity activity,
final @NotNull ILogger logger,
final @NotNull BuildInfoProvider buildInfoProvider) {
- return takeScreenshot(
- activity, AndroidMainThreadChecker.getInstance(), logger, buildInfoProvider);
+ return takeScreenshot(activity, AndroidThreadChecker.getInstance(), logger, buildInfoProvider);
}
@SuppressLint("NewApi")
public static @Nullable byte[] takeScreenshot(
final @NotNull Activity activity,
- final @NotNull IMainThreadChecker mainThreadChecker,
+ final @NotNull IThreadChecker threadChecker,
final @NotNull ILogger logger,
final @NotNull BuildInfoProvider buildInfoProvider) {
// We are keeping BuildInfoProvider param for compatibility, as it's being used by
@@ -113,7 +112,7 @@ public class ScreenshotUtils {
}
} else {
final Canvas canvas = new Canvas(bitmap);
- if (mainThreadChecker.isMainThread()) {
+ if (threadChecker.isMainThread()) {
view.draw(canvas);
latch.countDown();
} else {
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java
index 27731e48cf..25ff5da2bd 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/internal/util/SentryFrameMetricsCollector.java
@@ -17,6 +17,7 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.BuildInfoProvider;
+import io.sentry.android.core.ContextUtils;
import io.sentry.util.Objects;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
@@ -84,7 +85,9 @@ public SentryFrameMetricsCollector(
final @NotNull ILogger logger,
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull WindowFrameMetricsManager windowFrameMetricsManager) {
- Objects.requireNonNull(context, "The context is required");
+ final @NotNull Context appContext =
+ Objects.requireNonNull(
+ ContextUtils.getApplicationContext(context), "The context is required");
this.logger = Objects.requireNonNull(logger, "Logger is required");
this.buildInfoProvider =
Objects.requireNonNull(buildInfoProvider, "BuildInfoProvider is required");
@@ -92,7 +95,7 @@ public SentryFrameMetricsCollector(
Objects.requireNonNull(windowFrameMetricsManager, "WindowFrameMetricsManager is required");
// registerActivityLifecycleCallbacks is only available if Context is an AppContext
- if (!(context instanceof Application)) {
+ if (!(appContext instanceof Application)) {
return;
}
// FrameMetrics api is only available since sdk version N
@@ -110,7 +113,7 @@ public SentryFrameMetricsCollector(
// We have to register the lifecycle callback, even if no profile is started, otherwise when we
// start a profile, we wouldn't have the current activity and couldn't get the frameMetrics.
- ((Application) context).registerActivityLifecycleCallbacks(this);
+ ((Application) appContext).registerActivityLifecycleCallbacks(this);
// Most considerations regarding timestamps of frames are inspired from JankStats library:
// https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:metrics/metrics-performance/src/main/java/androidx/metrics/performance/JankStatsApi24Impl.kt
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java
index 5c29e95b63..996c6ab171 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java
@@ -1,18 +1,29 @@
package io.sentry.android.core.performance;
+import android.app.Activity;
import android.app.Application;
import android.content.ContentProvider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import io.sentry.ISentryLifecycleToken;
import io.sentry.ITransactionProfiler;
+import io.sentry.SentryDate;
+import io.sentry.SentryNanotimeDate;
import io.sentry.TracesSamplingDecision;
import io.sentry.android.core.ContextUtils;
import io.sentry.android.core.SentryAndroidOptions;
+import io.sentry.util.AutoClosableReentrantLock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
@@ -23,7 +34,7 @@
* transformed into SDK specific txn/span data structures.
*/
@ApiStatus.Internal
-public class AppStartMetrics {
+public class AppStartMetrics extends ActivityLifecycleCallbacksAdapter {
public enum AppStartType {
UNKNOWN,
@@ -34,6 +45,8 @@ public enum AppStartType {
private static long CLASS_LOADED_UPTIME_MS = SystemClock.uptimeMillis();
private static volatile @Nullable AppStartMetrics instance;
+ public static final @NotNull AutoClosableReentrantLock staticLock =
+ new AutoClosableReentrantLock();
private @NotNull AppStartType appStartType = AppStartType.UNKNOWN;
private boolean appLaunchedInForeground = false;
@@ -45,11 +58,13 @@ public enum AppStartType {
private final @NotNull List activityLifecycles;
private @Nullable ITransactionProfiler appStartProfiler = null;
private @Nullable TracesSamplingDecision appStartSamplingDecision = null;
+ private @Nullable SentryDate onCreateTime = null;
+ private boolean appLaunchTooLong = false;
+ private boolean isCallbackRegistered = false;
public static @NotNull AppStartMetrics getInstance() {
-
if (instance == null) {
- synchronized (AppStartMetrics.class) {
+ try (final @NotNull ISentryLifecycleToken ignored = staticLock.acquire()) {
if (instance == null) {
instance = new AppStartMetrics();
}
@@ -65,6 +80,7 @@ public AppStartMetrics() {
applicationOnCreate = new TimeSpan();
contentProviderOnCreates = new HashMap<>();
activityLifecycles = new ArrayList<>();
+ appLaunchedInForeground = ContextUtils.isForegroundImportance();
}
/**
@@ -102,6 +118,11 @@ public boolean isAppLaunchedInForeground() {
return appLaunchedInForeground;
}
+ @VisibleForTesting
+ public void setAppLaunchedInForeground(final boolean appLaunchedInForeground) {
+ this.appLaunchedInForeground = appLaunchedInForeground;
+ }
+
/**
* Provides all collected content provider onCreate time spans
*
@@ -137,12 +158,20 @@ public long getClassLoadedUptimeMs() {
// Only started when sdk version is >= N
final @NotNull TimeSpan appStartSpan = getAppStartTimeSpan();
if (appStartSpan.hasStarted()) {
- return appStartSpan;
+ return validateAppStartSpan(appStartSpan);
}
}
// fallback: use sdk init time span, as it will always have a start time set
- return getSdkInitTimeSpan();
+ return validateAppStartSpan(getSdkInitTimeSpan());
+ }
+
+ private @NotNull TimeSpan validateAppStartSpan(final @NotNull TimeSpan appStartSpan) {
+ // If the app launch took too long or it was launched in the background we return an empty span
+ if (appLaunchTooLong || !appLaunchedInForeground) {
+ return new TimeSpan();
+ }
+ return appStartSpan;
}
@TestOnly
@@ -158,6 +187,10 @@ public void clear() {
}
appStartProfiler = null;
appStartSamplingDecision = null;
+ appLaunchTooLong = false;
+ appLaunchedInForeground = false;
+ onCreateTime = null;
+ isCallbackRegistered = false;
}
public @Nullable ITransactionProfiler getAppStartProfiler() {
@@ -195,7 +228,64 @@ public static void onApplicationCreate(final @NotNull Application application) {
final @NotNull AppStartMetrics instance = getInstance();
if (instance.applicationOnCreate.hasNotStarted()) {
instance.applicationOnCreate.setStartedAt(now);
- instance.appLaunchedInForeground = ContextUtils.isForegroundImportance();
+ instance.registerApplicationForegroundCheck(application);
+ }
+ }
+
+ /**
+ * Register a callback to check if an activity was started after the application was created
+ *
+ * @param application The application object to register the callback to
+ */
+ public void registerApplicationForegroundCheck(final @NotNull Application application) {
+ if (isCallbackRegistered) {
+ return;
+ }
+ isCallbackRegistered = true;
+ appLaunchedInForeground = appLaunchedInForeground || ContextUtils.isForegroundImportance();
+ application.registerActivityLifecycleCallbacks(instance);
+ // We post on the main thread a task to post a check on the main thread. On Pixel devices
+ // (possibly others) the first task posted on the main thread is called before the
+ // Activity.onCreate callback. This is a workaround for that, so that the Activity.onCreate
+ // callback is called before the application one.
+ new Handler(Looper.getMainLooper()).post(() -> checkCreateTimeOnMain(application));
+ }
+
+ private void checkCreateTimeOnMain(final @NotNull Application application) {
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ // if no activity has ever been created, app was launched in background
+ if (onCreateTime == null) {
+ appLaunchedInForeground = false;
+
+ // we stop the app start profiler, as it's useless and likely to timeout
+ if (appStartProfiler != null && appStartProfiler.isRunning()) {
+ appStartProfiler.close();
+ appStartProfiler = null;
+ }
+ }
+ application.unregisterActivityLifecycleCallbacks(instance);
+ });
+ }
+
+ @Override
+ public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
+ // An activity already called onCreate()
+ if (!appLaunchedInForeground || onCreateTime != null) {
+ return;
+ }
+ onCreateTime = new SentryNanotimeDate();
+
+ final long spanStartMillis = appStartSpan.getStartTimestampMs();
+ final long spanEndMillis =
+ appStartSpan.hasStopped()
+ ? appStartSpan.getProjectedStopTimestampMs()
+ : System.currentTimeMillis();
+ final long durationMillis = spanEndMillis - spanStartMillis;
+ // If the app was launched more than 1 minute ago, it's likely wrong
+ if (durationMillis > TimeUnit.MINUTES.toMillis(1)) {
+ appLaunchTooLong = true;
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt
index 10dc60e74b..56dabd2fbc 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt
@@ -4,7 +4,7 @@ import android.app.Activity
import android.app.Application
import android.os.Bundle
import io.sentry.Breadcrumb
-import io.sentry.Hub
+import io.sentry.Scopes
import io.sentry.SentryLevel
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
@@ -20,7 +20,7 @@ class ActivityBreadcrumbsIntegrationTest {
private class Fixture {
val application = mock()
- val hub = mock()
+ val scopes = mock()
val options = SentryAndroidOptions().apply {
dsn = "https://key@sentry.io/proj"
}
@@ -28,7 +28,7 @@ class ActivityBreadcrumbsIntegrationTest {
fun getSut(enabled: Boolean = true): ActivityBreadcrumbsIntegration {
options.isEnableActivityLifecycleBreadcrumbs = enabled
- whenever(hub.options).thenReturn(options)
+ whenever(scopes.options).thenReturn(options)
return ActivityBreadcrumbsIntegration(
application
)
@@ -40,7 +40,7 @@ class ActivityBreadcrumbsIntegrationTest {
@Test
fun `When ActivityBreadcrumbsIntegration is disabled, it should not register the activity callback`() {
val sut = fixture.getSut(false)
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
verify(fixture.application, never()).registerActivityLifecycleCallbacks(any())
}
@@ -48,7 +48,7 @@ class ActivityBreadcrumbsIntegrationTest {
@Test
fun `When ActivityBreadcrumbsIntegration is enabled, it should register the activity callback`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
verify(fixture.application).registerActivityLifecycleCallbacks(any())
@@ -59,12 +59,12 @@ class ActivityBreadcrumbsIntegrationTest {
@Test
fun `When breadcrumb is added, type and category should be set`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).addBreadcrumb(
+ verify(fixture.scopes).addBreadcrumb(
check {
assertEquals("ui.lifecycle", it.category)
assertEquals("navigation", it.type)
@@ -78,77 +78,77 @@ class ActivityBreadcrumbsIntegrationTest {
@Test
fun `When activity is created, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is started, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityStarted(activity)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is resumed, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityResumed(activity)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is paused, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityPaused(activity)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is stopped, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityStopped(activity)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is save instance, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivitySaveInstanceState(activity, fixture.bundle)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
@Test
fun `When activity is destroyed, it should add a breadcrumb`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityDestroyed(activity)
- verify(fixture.hub).addBreadcrumb(any(), anyOrNull())
+ verify(fixture.scopes).addBreadcrumb(any(), anyOrNull())
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt
index f42d903415..db9912c052 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityFramesTrackerTest.kt
@@ -30,6 +30,11 @@ class ActivityFramesTrackerTest {
val handler = mock()
val options = SentryAndroidOptions()
+ init {
+ // ActivityFramesTracker is used only if performanceV2 is disabled
+ options.isEnablePerformanceV2 = false
+ }
+
fun getSut(mockAggregator: Boolean = true): ActivityFramesTracker {
return if (mockAggregator) {
ActivityFramesTracker(loadClass, options, handler, aggregator)
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 f936b6251c..675626522c 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
@@ -14,10 +14,10 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.DateUtils
import io.sentry.FullyDisplayedReporter
-import io.sentry.Hub
import io.sentry.IScope
import io.sentry.Scope
import io.sentry.ScopeCallback
+import io.sentry.Scopes
import io.sentry.Sentry
import io.sentry.SentryDate
import io.sentry.SentryDateProvider
@@ -54,6 +54,7 @@ import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowActivityManager
import java.util.Date
import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
@@ -71,13 +72,12 @@ class ActivityLifecycleIntegrationTest {
private class Fixture {
val application = mock()
- val hub = mock()
+ val scopes = mock()
val options = SentryAndroidOptions().apply {
dsn = "https://key@sentry.io/proj"
}
val bundle = mock()
val activityFramesTracker = mock()
- val fullyDisplayedReporter = FullyDisplayedReporter.getInstance()
val transactionFinishedCallback = mock()
lateinit var shadowActivityManager: ShadowActivityManager
@@ -92,13 +92,14 @@ class ActivityLifecycleIntegrationTest {
): ActivityLifecycleIntegration {
initializer?.configure(options)
- whenever(hub.options).thenReturn(options)
+ whenever(scopes.options).thenReturn(options)
+ AppStartMetrics.getInstance().isAppLaunchedInForeground = true
// We let the ActivityLifecycleIntegration create the proper transaction here
val optionCaptor = argumentCaptor()
val contextCaptor = argumentCaptor()
- whenever(hub.startTransaction(contextCaptor.capture(), optionCaptor.capture())).thenAnswer {
- val t = SentryTracer(contextCaptor.lastValue, hub, optionCaptor.lastValue)
+ whenever(scopes.startTransaction(contextCaptor.capture(), optionCaptor.capture())).thenAnswer {
+ val t = SentryTracer(contextCaptor.lastValue, scopes, optionCaptor.lastValue)
transaction = t
return@thenAnswer t
}
@@ -145,7 +146,7 @@ class ActivityLifecycleIntegrationTest {
@Test
fun `When ActivityLifecycleIntegration is registered, it registers activity callback`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
verify(fixture.application).registerActivityLifecycleCallbacks(any())
}
@@ -153,7 +154,7 @@ class ActivityLifecycleIntegrationTest {
@Test
fun `When ActivityLifecycleIntegration is closed, it should unregister the callback`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.close()
@@ -163,7 +164,7 @@ class ActivityLifecycleIntegrationTest {
@Test
fun `When ActivityLifecycleIntegration is closed, it should close the ActivityFramesTracker`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.close()
@@ -173,39 +174,39 @@ class ActivityLifecycleIntegrationTest {
@Test
fun `When tracing is disabled, do not start tracing`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub, never()).startTransaction(any(), any())
+ verify(fixture.scopes, never()).startTransaction(any(), any())
}
@Test
fun `When tracing is enabled but activity is running, do not start tracing again`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(any(), any())
+ verify(fixture.scopes).startTransaction(any(), any())
}
@Test
fun `Transaction op is ui_load and idle+deadline timeouts are set`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
check {
assertEquals("ui.load", it.operation)
assertEquals(TransactionNameSource.COMPONENT, it.transactionNameSource)
@@ -213,6 +214,7 @@ class ActivityLifecycleIntegrationTest {
check { transactionOptions ->
assertEquals(fixture.options.idleTimeout, transactionOptions.idleTimeout)
assertEquals(TransactionOptions.DEFAULT_DEADLINE_TIMEOUT_AUTO_TRANSACTION, transactionOptions.deadlineTimeout)
+ assertEquals("auto.ui.activity", transactionOptions.origin)
}
)
}
@@ -221,7 +223,7 @@ class ActivityLifecycleIntegrationTest {
fun `Activity gets added to ActivityFramesTracker during transaction creation`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityStarted(activity)
@@ -233,14 +235,14 @@ class ActivityLifecycleIntegrationTest {
fun `Transaction name is the Activity's name`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
check {
assertEquals("Activity", it.name)
assertEquals(TransactionNameSource.COMPONENT, it.transactionNameSource)
@@ -254,9 +256,9 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
- whenever(fixture.hub.configureScope(any())).thenAnswer {
+ whenever(fixture.scopes.configureScope(any())).thenAnswer {
val scope = Scope(fixture.options)
sut.applyScope(scope, fixture.transaction)
@@ -273,11 +275,11 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
- whenever(fixture.hub.configureScope(any())).thenAnswer {
+ whenever(fixture.scopes.configureScope(any())).thenAnswer {
val scope = Scope(fixture.options)
- val previousTransaction = SentryTracer(TransactionContext("name", "op"), fixture.hub)
+ val previousTransaction = SentryTracer(TransactionContext("name", "op"), fixture.scopes)
scope.transaction = previousTransaction
sut.applyScope(scope, fixture.transaction)
@@ -297,14 +299,14 @@ class ActivityLifecycleIntegrationTest {
it.isEnableTimeToFullDisplayTracing = true
it.idleTimeout = 200
})
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
sut.ttidSpanMap.values.first().finish()
sut.ttfdSpanMap.values.first().finish()
// then transaction should not be immediately finished
- verify(fixture.hub, never())
+ verify(fixture.scopes, never())
.captureTransaction(
anyOrNull(),
anyOrNull(),
@@ -316,7 +318,7 @@ class ActivityLifecycleIntegrationTest {
Thread.sleep(400)
// then the transaction should be finished
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
assertEquals(SpanStatus.OK, it.status)
},
@@ -330,13 +332,13 @@ class ActivityLifecycleIntegrationTest {
fun `When tracing auto finish is enabled, it doesn't stop the transaction on onActivityPostResumed`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityPostResumed(activity)
- verify(fixture.hub, never()).captureTransaction(
+ verify(fixture.scopes, never()).captureTransaction(
check {
assertEquals(SpanStatus.OK, it.status)
},
@@ -350,7 +352,7 @@ class ActivityLifecycleIntegrationTest {
fun `When tracing has status, do not overwrite it`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -360,7 +362,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityPostResumed(activity)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
assertEquals(SpanStatus.UNKNOWN_ERROR, it.status)
},
@@ -376,43 +378,43 @@ class ActivityLifecycleIntegrationTest {
it.tracesSampleRate = 1.0
it.isEnableActivityLifecycleTracingAutoFinish = false
})
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityPostResumed(activity)
- verify(fixture.hub, never()).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes, never()).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
}
@Test
fun `When tracing is disabled, do not finish transaction`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityPostResumed(activity)
- verify(fixture.hub, never()).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes, never()).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
}
@Test
fun `When Activity is destroyed but transaction is running, finish it`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
}
@Test
fun `When transaction is started, adds to WeakWef`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -424,7 +426,7 @@ class ActivityLifecycleIntegrationTest {
fun `When Activity is destroyed removes WeakRef`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -437,7 +439,7 @@ class ActivityLifecycleIntegrationTest {
fun `When Activity is destroyed, sets appStartSpan status to cancelled and finish it`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -454,7 +456,7 @@ class ActivityLifecycleIntegrationTest {
fun `When Activity is destroyed, sets appStartSpan to null`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -469,7 +471,7 @@ class ActivityLifecycleIntegrationTest {
fun `When Activity is destroyed, sets ttidSpan status to deadline_exceeded and finish it`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -486,7 +488,7 @@ class ActivityLifecycleIntegrationTest {
fun `When Activity is destroyed, sets ttidSpan to null`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -503,7 +505,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -521,7 +523,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
@@ -537,25 +539,25 @@ class ActivityLifecycleIntegrationTest {
fun `When new Activity and transaction is created, finish previous ones`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(mock(), mock())
sut.onActivityCreated(mock(), fixture.bundle)
- verify(fixture.hub).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes).captureTransaction(any(), anyOrNull(), anyOrNull(), anyOrNull())
}
@Test
fun `do not stop transaction on resumed if API 29`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, mock())
sut.onActivityResumed(activity)
- verify(fixture.hub, never()).captureTransaction(any(), any(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes, never()).captureTransaction(any(), any(), anyOrNull(), anyOrNull())
}
@Test
@@ -563,7 +565,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut(Build.VERSION_CODES.P)
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, mock())
@@ -571,21 +573,21 @@ class ActivityLifecycleIntegrationTest {
sut.ttfdSpanMap.values.first().finish()
sut.onActivityResumed(activity)
- verify(fixture.hub, never()).captureTransaction(any(), any(), anyOrNull(), anyOrNull())
+ verify(fixture.scopes, never()).captureTransaction(any(), any(), anyOrNull(), anyOrNull())
}
@Test
fun `start transaction on created if API less than 29`() {
val sut = fixture.getSut(Build.VERSION_CODES.P)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
setAppStartTime()
val activity = mock()
sut.onActivityCreated(activity, mock())
- verify(fixture.hub).startTransaction(any(), any())
+ verify(fixture.scopes).startTransaction(any(), any())
}
@Test
@@ -593,7 +595,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, mock())
@@ -611,22 +613,41 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, mock())
val ttfdSpan = sut.ttfdSpanMap[activity]
sut.ttidSpanMap.values.first().finish()
- fixture.fullyDisplayedReporter.reportFullyDrawn()
+ fixture.options.fullyDisplayedReporter.reportFullyDrawn()
assertTrue(ttfdSpan!!.isFinished)
assertNotEquals(SpanStatus.CANCELLED, ttfdSpan.status)
}
+ @Test
+ fun `if ttfd is disabled, no listener is registered for FullyDisplayedReporter`() {
+ val ttfdReporter = mock()
+
+ val sut = fixture.getSut()
+ fixture.options.apply {
+ tracesSampleRate = 1.0
+ isEnableTimeToFullDisplayTracing = false
+ fullyDisplayedReporter = ttfdReporter
+ }
+
+ sut.register(fixture.scopes, fixture.options)
+
+ val activity = mock()
+ sut.onActivityCreated(activity, mock())
+
+ verify(ttfdReporter, never()).registerFullyDrawnListener(any())
+ }
+
@Test
fun `App start is Cold when savedInstanceState is null`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, null)
@@ -638,7 +659,7 @@ class ActivityLifecycleIntegrationTest {
fun `App start is Warm when savedInstanceState is not null`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
val bundle = Bundle()
@@ -651,7 +672,7 @@ class ActivityLifecycleIntegrationTest {
fun `Do not overwrite App start type after set`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
val bundle = Bundle()
@@ -665,7 +686,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true, start transaction with given appStartTime`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -674,7 +695,7 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, fixture.bundle)
// call only once
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
any(),
check {
assertEquals(date.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
@@ -686,7 +707,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true and app start sampling decision is set, start transaction with isAppStart true`() {
AppStartMetrics.getInstance().appStartSamplingDecision = mock()
val sut = fixture.getSut { it.tracesSampleRate = 1.0 }
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -694,7 +715,7 @@ class ActivityLifecycleIntegrationTest {
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
any(),
check {
assertEquals(date.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
@@ -706,18 +727,22 @@ class ActivityLifecycleIntegrationTest {
@Test
fun `When firstActivityCreated is true and app start sampling decision is not set, start transaction with isAppStart false`() {
val sut = fixture.getSut { it.tracesSampleRate = 1.0 }
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
+ val date2 = SentryNanotimeDate(Date(2), 2)
setAppStartTime(date)
val activity = mock()
+ // The activity onCreate date will be ignored
+ fixture.options.dateProvider = SentryDateProvider { date2 }
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
any(),
check {
assertEquals(date.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
+ assertNotEquals(date2.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
assertFalse(it.isAppStartTransaction)
}
)
@@ -727,19 +752,19 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is false and app start sampling decision is set, start transaction with isAppStart false`() {
AppStartMetrics.getInstance().appStartSamplingDecision = mock()
val sut = fixture.getSut { it.tracesSampleRate = 1.0 }
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- verify(fixture.hub).startTransaction(any(), check { assertFalse(it.isAppStartTransaction) })
+ verify(fixture.scopes).startTransaction(any(), check { assertFalse(it.isAppStartTransaction) })
}
@Test
fun `When firstActivityCreated is true, do not create app start span if not foregroundImportance`() {
val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_BACKGROUND)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually set by SentryPerformanceProvider
val date = SentryNanotimeDate(Date(1), 0)
@@ -750,17 +775,41 @@ class ActivityLifecycleIntegrationTest {
sut.onActivityCreated(activity, fixture.bundle)
// call only once
- verify(fixture.hub).startTransaction(
+ verify(fixture.scopes).startTransaction(
any(),
check { assertNotEquals(date, it.startTimestamp) }
)
}
+ @Test
+ fun `When firstActivityCreated is true and no app start time is set, default to onActivityCreated time`() {
+ val sut = fixture.getSut()
+ fixture.options.tracesSampleRate = 1.0
+ sut.register(fixture.scopes, fixture.options)
+
+ // usually set by SentryPerformanceProvider
+ val date = SentryNanotimeDate(Date(1), 0)
+ val date2 = SentryNanotimeDate(Date(2), 2)
+
+ val activity = mock()
+ // Activity onCreate date will be used
+ fixture.options.dateProvider = SentryDateProvider { date2 }
+ sut.onActivityCreated(activity, fixture.bundle)
+
+ verify(fixture.scopes).startTransaction(
+ any(),
+ check {
+ assertEquals(date2.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
+ assertNotEquals(date.nanoTimestamp(), it.startTimestamp!!.nanoTimestamp())
+ }
+ )
+ }
+
@Test
fun `Create and finish app start span immediately in case SDK init is deferred`() {
val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually set by SentryPerformanceProvider
val startDate = SentryNanotimeDate(Date(1), 0)
@@ -768,6 +817,7 @@ class ActivityLifecycleIntegrationTest {
val appStartMetrics = AppStartMetrics.getInstance()
appStartMetrics.appStartType = AppStartType.WARM
appStartMetrics.sdkInitTimeSpan.setStoppedAt(2)
+ appStartMetrics.appStartTimeSpan.setStoppedAt(2)
val endDate = appStartMetrics.sdkInitTimeSpan.projectedStopTimestamp
@@ -786,7 +836,7 @@ class ActivityLifecycleIntegrationTest {
fun `When SentryPerformanceProvider is disabled, app start time span is still created`() {
val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually done by SentryPerformanceProvider, if disabled it's done by
// SentryAndroid.init
@@ -814,7 +864,7 @@ class ActivityLifecycleIntegrationTest {
fun `When app-start end time is already set, it should not be overwritten`() {
val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually done by SentryPerformanceProvider
val startDate = SentryNanotimeDate(Date(1), 0)
@@ -838,7 +888,7 @@ class ActivityLifecycleIntegrationTest {
fun `When activity lifecycle happens multiple times, app-start end time should not be overwritten`() {
val sut = fixture.getSut(importance = RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually done by SentryPerformanceProvider
val startDate = SentryNanotimeDate(Date(1), 0)
@@ -876,7 +926,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true, start app start warm span with given appStartTime`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -893,7 +943,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true, start app start cold span with given appStartTime`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -910,7 +960,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true, start app start span with Warm description`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -927,7 +977,7 @@ class ActivityLifecycleIntegrationTest {
fun `When firstActivityCreated is true, start app start span with Cold description`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)
@@ -940,11 +990,51 @@ class ActivityLifecycleIntegrationTest {
assertEquals(span.startDate.nanoTimestamp(), date.nanoTimestamp())
}
+ @Test
+ fun `When firstActivityCreated is true and app started more than 1 minute ago, app start spans are dropped`() {
+ val sut = fixture.getSut()
+ fixture.options.tracesSampleRate = 1.0
+ sut.register(fixture.scopes, fixture.options)
+
+ val date = SentryNanotimeDate(Date(1), 0)
+ val duration = TimeUnit.MINUTES.toMillis(1) + 2
+ val durationNanos = TimeUnit.MILLISECONDS.toNanos(duration)
+ val stopDate = SentryNanotimeDate(Date(duration), durationNanos)
+ setAppStartTime(date, stopDate)
+
+ val activity = mock()
+ sut.onActivityCreated(activity, null)
+
+ val appStartSpan = fixture.transaction.children.firstOrNull {
+ it.description == "Cold Start"
+ }
+ assertNull(appStartSpan)
+ }
+
+ @Test
+ fun `When firstActivityCreated is true and app started in background, app start spans are dropped`() {
+ val sut = fixture.getSut()
+ AppStartMetrics.getInstance().isAppLaunchedInForeground = false
+ fixture.options.tracesSampleRate = 1.0
+ sut.register(fixture.scopes, fixture.options)
+
+ val date = SentryNanotimeDate(Date(1), 0)
+ setAppStartTime(date)
+
+ val activity = mock()
+ sut.onActivityCreated(activity, null)
+
+ val appStartSpan = fixture.transaction.children.firstOrNull {
+ it.description == "Cold Start"
+ }
+ assertNull(appStartSpan)
+ }
+
@Test
fun `When firstActivityCreated is false, start transaction but not with given appStartTime`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime()
@@ -965,12 +1055,12 @@ class ActivityLifecycleIntegrationTest {
fun `When transaction is finished, it gets removed from scope`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
- whenever(fixture.hub.configureScope(any())).thenAnswer {
+ whenever(fixture.scopes.configureScope(any())).thenAnswer {
val scope = Scope(fixture.options)
scope.transaction = fixture.transaction
@@ -988,7 +1078,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = false
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -1001,7 +1091,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -1016,7 +1106,7 @@ class ActivityLifecycleIntegrationTest {
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
fixture.options.executorService = deferredExecutorService
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
val ttfdSpan = sut.ttfdSpanMap[activity]
@@ -1032,7 +1122,7 @@ class ActivityLifecycleIntegrationTest {
assertEquals(SpanStatus.DEADLINE_EXCEEDED, ttfdSpan.status)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
// ttfd timed out, so its measurement should not be set
val ttfdMeasurement = it.measurements[MeasurementValue.KEY_TIME_TO_FULL_DISPLAY]
@@ -1049,7 +1139,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
val ttfdSpan = sut.ttfdSpanMap[activity]
@@ -1072,7 +1162,7 @@ class ActivityLifecycleIntegrationTest {
assertNull(autoCloseFuture)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
// ttfd was finished successfully, so its measurement should be set
val ttfdMeasurement = it.measurements[MeasurementValue.KEY_TIME_TO_FULL_DISPLAY]
@@ -1090,7 +1180,7 @@ class ActivityLifecycleIntegrationTest {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
val activity2 = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -1128,7 +1218,7 @@ class ActivityLifecycleIntegrationTest {
whenever(activity.findViewById(any())).thenReturn(view)
// Make the integration create the spans and register to the FirstDrawDoneListener
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityResumed(activity)
@@ -1143,7 +1233,7 @@ class ActivityLifecycleIntegrationTest {
assertTrue(ttidSpan.isFinished)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
// ttid measurement should be set
val ttidMeasurement = it.measurements[MeasurementValue.KEY_TIME_TO_INITIAL_DISPLAY]
@@ -1166,7 +1256,7 @@ class ActivityLifecycleIntegrationTest {
whenever(activity.findViewById(any())).thenReturn(view)
// Make the integration create the spans and register to the FirstDrawDoneListener
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityResumed(activity)
@@ -1189,7 +1279,7 @@ class ActivityLifecycleIntegrationTest {
assertEquals(newEndDate, ttidSpan.finishDate)
sut.onActivityDestroyed(activity)
- verify(fixture.hub).captureTransaction(
+ verify(fixture.scopes).captureTransaction(
check {
// ttid and ttfd measurements should be the same
val ttidMeasurement = it.measurements[MeasurementValue.KEY_TIME_TO_INITIAL_DISPLAY]
@@ -1211,7 +1301,7 @@ class ActivityLifecycleIntegrationTest {
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
// The ttid span should be running
@@ -1233,7 +1323,7 @@ class ActivityLifecycleIntegrationTest {
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
fixture.options.executorService = deferredExecutorService
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
sut.onActivityResumed(activity)
@@ -1268,7 +1358,7 @@ class ActivityLifecycleIntegrationTest {
fixture.options.tracesSampleRate = 1.0
fixture.options.isEnableTimeToFullDisplayTracing = true
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
sut.onActivityCreated(activity, fixture.bundle)
val ttfdSpan = sut.ttfdSpanMap[activity]
assertNotNull(ttfdSpan)
@@ -1289,20 +1379,20 @@ class ActivityLifecycleIntegrationTest {
fun `starts new trace if performance is disabled`() {
val sut = fixture.getSut()
val activity = mock()
- fixture.options.enableTracing = false
+ fixture.options.tracesSampleRate = null
val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java)
val scope = Scope(fixture.options)
val propagationContextAtStart = scope.propagationContext
- whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer {
+ whenever(fixture.scopes.configureScope(argumentCaptor.capture())).thenAnswer {
argumentCaptor.value.run(scope)
}
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, 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(fixture.scopes, times(2)).configureScope(any())
assertNotSame(propagationContextAtStart, scope.propagationContext)
}
@@ -1310,19 +1400,19 @@ class ActivityLifecycleIntegrationTest {
fun `sets the activity as the current screen`() {
val sut = fixture.getSut()
val activity = mock()
- fixture.options.enableTracing = false
+ fixture.options.tracesSampleRate = null
val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java)
val scope = mock()
- whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer {
+ whenever(fixture.scopes.configureScope(argumentCaptor.capture())).thenAnswer {
argumentCaptor.value.run(scope)
}
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, 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(fixture.scopes, times(2)).configureScope(any())
verify(scope).setScreen(any())
}
@@ -1330,37 +1420,37 @@ class ActivityLifecycleIntegrationTest {
fun `does not start another new trace if one has already been started but does after activity was destroyed`() {
val sut = fixture.getSut()
val activity = mock()
- fixture.options.enableTracing = false
+ fixture.options.tracesSampleRate = null
val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java)
val scope = Scope(fixture.options)
val propagationContextAtStart = scope.propagationContext
- whenever(fixture.hub.configureScope(argumentCaptor.capture())).thenAnswer {
+ whenever(fixture.scopes.configureScope(argumentCaptor.capture())).thenAnswer {
argumentCaptor.value.run(scope)
}
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, 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(fixture.scopes, times(2)).configureScope(any())
val propagationContextAfterNewTrace = scope.propagationContext
assertNotSame(propagationContextAtStart, propagationContextAfterNewTrace)
- clearInvocations(fixture.hub)
+ clearInvocations(fixture.scopes)
sut.onActivityCreated(activity, fixture.bundle)
// once for the screen, but not for the tracing propagation context
- verify(fixture.hub).configureScope(any())
+ verify(fixture.scopes).configureScope(any())
assertSame(propagationContextAfterNewTrace, scope.propagationContext)
sut.onActivityDestroyed(activity)
- clearInvocations(fixture.hub)
+ clearInvocations(fixture.scopes)
sut.onActivityCreated(activity, fixture.bundle)
// once for the screen, and once for the tracing propagation context
- verify(fixture.hub, times(2)).configureScope(any())
+ verify(fixture.scopes, times(2)).configureScope(any())
assertNotSame(propagationContextAfterNewTrace, scope.propagationContext)
}
@@ -1368,7 +1458,7 @@ class ActivityLifecycleIntegrationTest {
fun `when transaction is finished, sets frame metrics`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
val activity = mock()
sut.onActivityCreated(activity, fixture.bundle)
@@ -1384,7 +1474,7 @@ class ActivityLifecycleIntegrationTest {
fixture.options.tracesSampleRate = 1.0
fixture.options.dateProvider = SentryDateProvider { now }
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
// usually done by SentryPerformanceProvider
val startDate = SentryNanotimeDate(Date(5678), 910)
@@ -1412,18 +1502,22 @@ class ActivityLifecycleIntegrationTest {
shadowOf(Looper.getMainLooper()).idle()
}
- private fun setAppStartTime(date: SentryDate = SentryNanotimeDate(Date(1), 0)) {
+ private fun setAppStartTime(date: SentryDate = SentryNanotimeDate(Date(1), 0), stopDate: SentryDate? = null) {
// set by SentryPerformanceProvider so forcing it here
val sdkAppStartTimeSpan = AppStartMetrics.getInstance().sdkInitTimeSpan
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
val millis = DateUtils.nanosToMillis(date.nanoTimestamp().toDouble()).toLong()
+ val stopMillis = DateUtils.nanosToMillis(stopDate?.nanoTimestamp()?.toDouble() ?: 0.0).toLong()
sdkAppStartTimeSpan.setStartedAt(millis)
sdkAppStartTimeSpan.setStartUnixTimeMs(millis)
- sdkAppStartTimeSpan.setStoppedAt(0)
+ sdkAppStartTimeSpan.setStoppedAt(stopMillis)
appStartTimeSpan.setStartedAt(millis)
appStartTimeSpan.setStartUnixTimeMs(millis)
- appStartTimeSpan.setStoppedAt(0)
+ appStartTimeSpan.setStoppedAt(stopMillis)
+ if (stopDate != null) {
+ AppStartMetrics.getInstance().onActivityCreated(mock(), mock())
+ }
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt
index 7800063b35..fce84bc855 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidOptionsInitializerTest.kt
@@ -13,8 +13,9 @@ import io.sentry.SentryOptions
import io.sentry.android.core.cache.AndroidEnvelopeCache
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator
import io.sentry.android.core.internal.modules.AssetsModulesLoader
-import io.sentry.android.core.internal.util.AndroidMainThreadChecker
+import io.sentry.android.core.internal.util.AndroidThreadChecker
import io.sentry.android.fragment.FragmentLifecycleIntegration
+import io.sentry.android.replay.ReplayIntegration
import io.sentry.android.timber.SentryTimberIntegration
import io.sentry.cache.PersistingOptionsObserver
import io.sentry.cache.PersistingScopeObserver
@@ -83,6 +84,7 @@ class AndroidOptionsInitializerTest {
loadClass,
activityFramesTracker,
false,
+ false,
false
)
@@ -99,7 +101,8 @@ class AndroidOptionsInitializerTest {
minApi: Int = Build.VERSION_CODES.KITKAT,
classesToLoad: List = emptyList(),
isFragmentAvailable: Boolean = false,
- isTimberAvailable: Boolean = false
+ isTimberAvailable: Boolean = false,
+ isReplayAvailable: Boolean = false
) {
mockContext = ContextUtilsTestHelper.mockMetaData(
mockContext = ContextUtilsTestHelper.createMockContext(hasAppContext = true),
@@ -126,7 +129,8 @@ class AndroidOptionsInitializerTest {
loadClass,
activityFramesTracker,
isFragmentAvailable,
- isTimberAvailable
+ isTimberAvailable,
+ isReplayAvailable
)
AndroidOptionsInitializer.initializeIntegrationsAndProcessors(
@@ -478,6 +482,31 @@ class AndroidOptionsInitializerTest {
assertNull(actual)
}
+ @Test
+ fun `ReplayIntegration added to the integration list if available on classpath`() {
+ fixture.initSutWithClassLoader(isReplayAvailable = true)
+
+ val actual =
+ fixture.sentryOptions.integrations.firstOrNull { it is ReplayIntegration }
+ assertNotNull(actual)
+ }
+
+ @Test
+ fun `ReplayIntegration set as ReplayController if available on classpath`() {
+ fixture.initSutWithClassLoader(isReplayAvailable = true)
+
+ assertTrue(fixture.sentryOptions.replayController is ReplayIntegration)
+ }
+
+ @Test
+ fun `ReplayIntegration won't be enabled, it throws class not found`() {
+ fixture.initSutWithClassLoader(isReplayAvailable = false)
+
+ val actual =
+ fixture.sentryOptions.integrations.firstOrNull { it is ReplayIntegration }
+ assertNull(actual)
+ }
+
@Test
fun `AndroidEnvelopeCache is set to options`() {
fixture.initSut()
@@ -495,7 +524,7 @@ class AndroidOptionsInitializerTest {
}
@Test
- fun `When Activity Frames Tracking is enabled, the Activity Frames Tracker should be available`() {
+ fun `When Activity Frames Tracking is enabled, the Activity Frames Tracker should be unavailable`() {
fixture.initSut(
hasAppContext = true,
useRealContext = true,
@@ -504,6 +533,25 @@ class AndroidOptionsInitializerTest {
}
)
+ val activityLifeCycleIntegration = fixture.sentryOptions.integrations
+ .first { it is ActivityLifecycleIntegration }
+
+ assertFalse(
+ (activityLifeCycleIntegration as ActivityLifecycleIntegration).activityFramesTracker.isFrameMetricsAggregatorAvailable
+ )
+ }
+
+ @Test
+ fun `When Activity Frames Tracking is enabled, the Activity Frames Tracker should be available if perfv2 is false`() {
+ fixture.initSut(
+ hasAppContext = true,
+ useRealContext = true,
+ configureOptions = {
+ isEnablePerformanceV2 = false
+ isEnableFramesTracking = true
+ }
+ )
+
val activityLifeCycleIntegration = fixture.sentryOptions.integrations
.first { it is ActivityLifecycleIntegration }
@@ -527,12 +575,32 @@ class AndroidOptionsInitializerTest {
}
@Test
- fun `When Frames Tracking is initially disabled, but enabled via configureOptions it should be available`() {
+ fun `When Frames Tracking is initially disabled, but enabled via configureOptions it should be unavailable`() {
+ fixture.sentryOptions.isEnableFramesTracking = false
+ fixture.initSut(
+ hasAppContext = true,
+ useRealContext = true,
+ configureOptions = {
+ isEnableFramesTracking = true
+ }
+ )
+
+ val activityLifeCycleIntegration = fixture.sentryOptions.integrations
+ .first { it is ActivityLifecycleIntegration }
+
+ assertFalse(
+ (activityLifeCycleIntegration as ActivityLifecycleIntegration).activityFramesTracker.isFrameMetricsAggregatorAvailable
+ )
+ }
+
+ @Test
+ fun `When Frames Tracking is initially disabled, but enabled via configureOptions it should be available if perfv2 is false`() {
fixture.sentryOptions.isEnableFramesTracking = false
fixture.initSut(
hasAppContext = true,
useRealContext = true,
configureOptions = {
+ isEnablePerformanceV2 = false
isEnableFramesTracking = true
}
)
@@ -553,10 +621,10 @@ class AndroidOptionsInitializerTest {
}
@Test
- fun `AndroidMainThreadChecker is set to options`() {
+ fun `AndroidThreadChecker is set to options`() {
fixture.initSut()
- assertTrue { fixture.sentryOptions.mainThreadChecker is AndroidMainThreadChecker }
+ assertTrue { fixture.sentryOptions.threadChecker is AndroidThreadChecker }
}
@Test
@@ -634,6 +702,7 @@ class AndroidOptionsInitializerTest {
mock(),
mock(),
false,
+ false,
false
)
verify(mockOptions, never()).outboxPath
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt
index 8219a273d0..c5bb334bb3 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidProfilerTest.kt
@@ -118,6 +118,7 @@ class AndroidProfilerTest {
loadClass,
activityFramesTracker,
false,
+ false,
false
)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt
index fd03d34631..86f95b4430 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AndroidTransactionProfilerTest.kt
@@ -5,8 +5,8 @@ import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.CpuCollectionData
-import io.sentry.IHub
import io.sentry.ILogger
+import io.sentry.IScopes
import io.sentry.ISentryExecutorService
import io.sentry.MemoryCollectionData
import io.sentry.PerformanceCollectionData
@@ -89,7 +89,7 @@ class AndroidTransactionProfilerTest {
executorService = mockExecutorService
}
- val hub: IHub = mock()
+ val scopes: IScopes = mock()
val frameMetricsCollector: SentryFrameMetricsCollector = mock()
lateinit var transaction1: SentryTracer
@@ -97,10 +97,10 @@ class AndroidTransactionProfilerTest {
lateinit var transaction3: SentryTracer
fun getSut(context: Context, buildInfoProvider: BuildInfoProvider = buildInfo): AndroidTransactionProfiler {
- whenever(hub.options).thenReturn(options)
- transaction1 = SentryTracer(TransactionContext("", ""), hub)
- transaction2 = SentryTracer(TransactionContext("", ""), hub)
- transaction3 = SentryTracer(TransactionContext("", ""), hub)
+ whenever(scopes.options).thenReturn(options)
+ transaction1 = SentryTracer(TransactionContext("", ""), scopes)
+ transaction2 = SentryTracer(TransactionContext("", ""), scopes)
+ transaction3 = SentryTracer(TransactionContext("", ""), scopes)
return AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector)
}
}
@@ -125,6 +125,7 @@ class AndroidTransactionProfilerTest {
loadClass,
activityFramesTracker,
false,
+ false,
false
)
@@ -335,16 +336,6 @@ class AndroidTransactionProfilerTest {
assertEquals(0, profiler.transactionsCounter)
}
- @Test
- fun `profiler ignores profilingTracesIntervalMillis`() {
- fixture.options.apply {
- profilingTracesIntervalMillis = 0
- }
- val profiler = fixture.getSut(context)
- profiler.start()
- assertEquals(1, profiler.transactionsCounter)
- }
-
@Test
fun `profiler never use background threads`() {
val profiler = fixture.getSut(context)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt
index cceabc9774..1a74a47ae1 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrIntegrationTest.kt
@@ -2,7 +2,7 @@ package io.sentry.android.core
import android.content.Context
import io.sentry.Hint
-import io.sentry.IHub
+import io.sentry.IScopes
import io.sentry.SentryLevel
import io.sentry.android.core.AnrIntegration.AnrHint
import io.sentry.exception.ExceptionMechanismException
@@ -24,7 +24,7 @@ class AnrIntegrationTest {
private class Fixture {
val context = mock()
- val hub = mock()
+ val scopes = mock()
var options: SentryAndroidOptions = SentryAndroidOptions().apply {
setLogger(mock())
}
@@ -49,7 +49,7 @@ class AnrIntegrationTest {
fixture.options.executorService = ImmediateExecutorService()
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.anrWatchDog)
assertTrue((sut.anrWatchDog as ANRWatchDog).isAlive)
@@ -60,7 +60,7 @@ class AnrIntegrationTest {
fixture.options.executorService = mock()
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNull(sut.anrWatchDog)
}
@@ -70,7 +70,7 @@ class AnrIntegrationTest {
val sut = fixture.getSut()
fixture.options.isAnrEnabled = false
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNull(sut.anrWatchDog)
}
@@ -79,9 +79,9 @@ class AnrIntegrationTest {
fun `When ANR watch dog is triggered, it should capture an error event with AnrHint`() {
val sut = fixture.getSut()
- sut.reportANR(fixture.hub, fixture.options, getApplicationNotResponding())
+ sut.reportANR(fixture.scopes, fixture.options, getApplicationNotResponding())
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
check {
assertEquals(SentryLevel.ERROR, it.level)
},
@@ -97,7 +97,7 @@ class AnrIntegrationTest {
val sut = fixture.getSut()
fixture.options.executorService = ImmediateExecutorService()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.anrWatchDog)
@@ -107,11 +107,11 @@ class AnrIntegrationTest {
}
@Test
- fun `when hub is closed right after start, integration is not registered`() {
+ fun `when scopes is closed right after start, integration is not registered`() {
val deferredExecutorService = DeferredExecutorService()
val sut = fixture.getSut()
fixture.options.executorService = deferredExecutorService
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNull(sut.anrWatchDog)
sut.close()
deferredExecutorService.runAll()
@@ -122,9 +122,9 @@ class AnrIntegrationTest {
fun `When ANR watch dog is triggered, constructs exception with proper mechanism and snapshot flag`() {
val sut = fixture.getSut()
- sut.reportANR(fixture.hub, fixture.options, getApplicationNotResponding())
+ sut.reportANR(fixture.scopes, fixture.options, getApplicationNotResponding())
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
check {
val ex = it.throwableMechanism as ExceptionMechanismException
assertTrue(ex.isSnapshot)
@@ -139,9 +139,9 @@ class AnrIntegrationTest {
val sut = fixture.getSut()
AppState.getInstance().setInBackground(true)
- sut.reportANR(fixture.hub, fixture.options, getApplicationNotResponding())
+ sut.reportANR(fixture.scopes, fixture.options, getApplicationNotResponding())
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
check {
val message = it.throwable?.message
assertTrue(message?.startsWith("Background") == true)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt
index b581856fe0..d930333f4c 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2EventProcessorTest.kt
@@ -15,18 +15,20 @@ import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryLevel.DEBUG
import io.sentry.SpanContext
-import io.sentry.cache.PersistingOptionsObserver
import io.sentry.cache.PersistingOptionsObserver.DIST_FILENAME
import io.sentry.cache.PersistingOptionsObserver.ENVIRONMENT_FILENAME
import io.sentry.cache.PersistingOptionsObserver.OPTIONS_CACHE
import io.sentry.cache.PersistingOptionsObserver.PROGUARD_UUID_FILENAME
import io.sentry.cache.PersistingOptionsObserver.RELEASE_FILENAME
+import io.sentry.cache.PersistingOptionsObserver.REPLAY_ERROR_SAMPLE_RATE_FILENAME
import io.sentry.cache.PersistingOptionsObserver.SDK_VERSION_FILENAME
+import io.sentry.cache.PersistingScopeObserver
import io.sentry.cache.PersistingScopeObserver.BREADCRUMBS_FILENAME
import io.sentry.cache.PersistingScopeObserver.CONTEXTS_FILENAME
import io.sentry.cache.PersistingScopeObserver.EXTRAS_FILENAME
import io.sentry.cache.PersistingScopeObserver.FINGERPRINT_FILENAME
import io.sentry.cache.PersistingScopeObserver.LEVEL_FILENAME
+import io.sentry.cache.PersistingScopeObserver.REPLAY_FILENAME
import io.sentry.cache.PersistingScopeObserver.REQUEST_FILENAME
import io.sentry.cache.PersistingScopeObserver.SCOPE_CACHE
import io.sentry.cache.PersistingScopeObserver.TAGS_FILENAME
@@ -44,6 +46,7 @@ import io.sentry.protocol.OperatingSystem
import io.sentry.protocol.Request
import io.sentry.protocol.Response
import io.sentry.protocol.SdkVersion
+import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryStackFrame
import io.sentry.protocol.SentryStackTrace
import io.sentry.protocol.SentryThread
@@ -75,7 +78,9 @@ class AnrV2EventProcessorTest {
val tmpDir = TemporaryFolder()
class Fixture {
-
+ companion object {
+ const val REPLAY_ID = "64cf554cc8d74c6eafa3e08b7c984f6d"
+ }
val buildInfo = mock()
lateinit var context: Context
val options = SentryAndroidOptions().apply {
@@ -87,7 +92,8 @@ class AnrV2EventProcessorTest {
dir: TemporaryFolder,
currentSdk: Int = Build.VERSION_CODES.LOLLIPOP,
populateScopeCache: Boolean = false,
- populateOptionsCache: Boolean = false
+ populateOptionsCache: Boolean = false,
+ replayErrorSampleRate: Double? = null
): AnrV2EventProcessor {
options.cacheDirPath = dir.newFolder().absolutePath
options.environment = "release"
@@ -109,7 +115,7 @@ class AnrV2EventProcessorTest {
persistScope(
CONTEXTS_FILENAME,
Contexts().apply {
- trace = SpanContext("test")
+ setTrace(SpanContext("test"))
setResponse(Response().apply { bodySize = 1024 })
setBrowser(Browser().apply { name = "Google Chrome" })
}
@@ -118,6 +124,7 @@ class AnrV2EventProcessorTest {
REQUEST_FILENAME,
Request().apply { url = "google.com"; method = "GET" }
)
+ persistScope(REPLAY_FILENAME, SentryId(REPLAY_ID))
}
if (populateOptionsCache) {
@@ -126,7 +133,10 @@ class AnrV2EventProcessorTest {
persistOptions(SDK_VERSION_FILENAME, SdkVersion("sentry.java.android", "6.15.0"))
persistOptions(DIST_FILENAME, "232")
persistOptions(ENVIRONMENT_FILENAME, "debug")
- persistOptions(PersistingOptionsObserver.TAGS_FILENAME, mapOf("option" to "tag"))
+ persistOptions(TAGS_FILENAME, mapOf("option" to "tag"))
+ replayErrorSampleRate?.let {
+ persistOptions(REPLAY_ERROR_SAMPLE_RATE_FILENAME, it.toString())
+ }
}
return AnrV2EventProcessor(context, options, buildInfo)
@@ -169,7 +179,7 @@ class AnrV2EventProcessorTest {
assertNull(processed.platform)
assertNull(processed.exceptions)
- assertEquals(emptyMap(), processed.contexts)
+ assertTrue(processed.contexts.isEmpty)
}
@Test
@@ -544,6 +554,65 @@ class AnrV2EventProcessorTest {
assertEquals(listOf("{{ default }}", "foreground-anr"), processedForeground.fingerprints)
}
+ @Test
+ fun `sets replayId when replay folder exists`() {
+ val hint = HintUtils.createWithTypeCheckHint(BackfillableHint())
+ val processor = fixture.getSut(tmpDir, populateScopeCache = true)
+ val replayFolder = File(fixture.options.cacheDirPath, "replay_${Fixture.REPLAY_ID}").also { it.mkdirs() }
+
+ val processed = processor.process(SentryEvent(), hint)!!
+
+ assertEquals(Fixture.REPLAY_ID, processed.contexts[Contexts.REPLAY_ID].toString())
+ }
+
+ @Test
+ fun `does not set replayId when replay folder does not exist and no sample rate persisted`() {
+ val hint = HintUtils.createWithTypeCheckHint(BackfillableHint())
+ val processor = fixture.getSut(tmpDir, populateScopeCache = true)
+ val replayId1 = SentryId()
+ val replayId2 = SentryId()
+
+ val replayFolder1 = File(fixture.options.cacheDirPath, "replay_$replayId1").also { it.mkdirs() }
+ val replayFolder2 = File(fixture.options.cacheDirPath, "replay_$replayId2").also { it.mkdirs() }
+
+ val processed = processor.process(SentryEvent(), hint)!!
+
+ assertNull(processed.contexts[Contexts.REPLAY_ID])
+ }
+
+ @Test
+ fun `does not set replayId when replay folder does not exist and not sampled`() {
+ val hint = HintUtils.createWithTypeCheckHint(BackfillableHint())
+ val processor = fixture.getSut(tmpDir, populateScopeCache = true, populateOptionsCache = true, replayErrorSampleRate = 0.0)
+ val replayId1 = SentryId()
+ val replayId2 = SentryId()
+
+ val replayFolder1 = File(fixture.options.cacheDirPath, "replay_$replayId1").also { it.mkdirs() }
+ val replayFolder2 = File(fixture.options.cacheDirPath, "replay_$replayId2").also { it.mkdirs() }
+
+ val processed = processor.process(SentryEvent(), hint)!!
+
+ assertNull(processed.contexts[Contexts.REPLAY_ID])
+ }
+
+ @Test
+ fun `set replayId of the last modified folder`() {
+ val hint = HintUtils.createWithTypeCheckHint(BackfillableHint())
+ val processor = fixture.getSut(tmpDir, populateScopeCache = true, populateOptionsCache = true, replayErrorSampleRate = 1.0)
+ val replayId1 = SentryId()
+ val replayId2 = SentryId()
+
+ val replayFolder1 = File(fixture.options.cacheDirPath, "replay_$replayId1").also { it.mkdirs() }
+ val replayFolder2 = File(fixture.options.cacheDirPath, "replay_$replayId2").also { it.mkdirs() }
+ replayFolder1.setLastModified(1000)
+ replayFolder2.setLastModified(500)
+
+ val processed = processor.process(SentryEvent(), hint)!!
+
+ assertEquals(replayId1.toString(), processed.contexts[Contexts.REPLAY_ID].toString())
+ assertEquals(replayId1.toString(), PersistingScopeObserver.read(fixture.options, REPLAY_FILENAME, String::class.java))
+ }
+
private fun processEvent(
hint: Hint,
populateScopeCache: Boolean = false,
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt
index 885ad22c8f..1abcd43719 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrV2IntegrationTest.kt
@@ -6,8 +6,8 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Hint
-import io.sentry.IHub
import io.sentry.ILogger
+import io.sentry.IScopes
import io.sentry.SentryEnvelope
import io.sentry.SentryLevel
import io.sentry.android.core.AnrV2Integration.AnrV2Hint
@@ -59,7 +59,7 @@ class AnrV2IntegrationTest {
lateinit var lastReportedAnrFile: File
val options = SentryAndroidOptions()
- val hub = mock()
+ val scopes = mock()
val logger = mock()
fun getSut(
@@ -93,7 +93,7 @@ class AnrV2IntegrationTest {
lastReportedAnrFile = File(cacheDir, AndroidEnvelopeCache.LAST_ANR_REPORT)
lastReportedAnrFile.writeText(lastReportedAnrTimestamp.toString())
}
- whenever(hub.captureEvent(any(), anyOrNull())).thenReturn(lastEventId)
+ whenever(scopes.captureEvent(any(), anyOrNull())).thenReturn(lastEventId)
return AnrV2Integration(context)
}
@@ -170,7 +170,7 @@ class AnrV2IntegrationTest {
fun `when cacheDir is not set, does not process historical exits`() {
val integration = fixture.getSut(null, useImmediateExecutorService = false)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
verify(fixture.options.executorService, never()).submit(any())
}
@@ -180,7 +180,7 @@ class AnrV2IntegrationTest {
val integration =
fixture.getSut(tmpDir, isAnrEnabled = false, useImmediateExecutorService = false)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
verify(fixture.options.executorService, never()).submit(any())
}
@@ -189,9 +189,9 @@ class AnrV2IntegrationTest {
fun `when historical exit list is empty, does not process historical exits`() {
val integration = fixture.getSut(tmpDir)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
@Test
@@ -199,9 +199,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir)
fixture.addAppExitInfo(reason = null)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
@Test
@@ -212,9 +212,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir)
fixture.addAppExitInfo(timestamp = oldTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
@Test
@@ -222,9 +222,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = oldTimestamp)
fixture.addAppExitInfo(timestamp = oldTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
@Test
@@ -232,9 +232,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = null)
fixture.addAppExitInfo(timestamp = oldTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes).captureEvent(any(), anyOrNull())
}
@Test
@@ -242,9 +242,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = oldTimestamp)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
check {
assertEquals(newTimestamp, it.timestamp.time)
assertEquals(SentryLevel.FATAL, it.level)
@@ -291,9 +291,9 @@ class AnrV2IntegrationTest {
importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
any(),
argThat {
val hint = HintUtils.getSentrySdkHint(this)
@@ -311,7 +311,7 @@ class AnrV2IntegrationTest {
)
fixture.addAppExitInfo(timestamp = newTimestamp)
- whenever(fixture.hub.captureEvent(any(), any())).thenAnswer { invocation ->
+ whenever(fixture.scopes.captureEvent(any(), any())).thenAnswer { invocation ->
val hint = HintUtils.getSentrySdkHint(invocation.getArgument(1))
as DiskFlushNotification
thread {
@@ -321,9 +321,9 @@ class AnrV2IntegrationTest {
SentryId()
}
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes).captureEvent(any(), anyOrNull())
// shouldn't fall into timed out state, because we marked event as flushed on another thread
verify(fixture.logger, never()).log(
any(),
@@ -341,9 +341,9 @@ class AnrV2IntegrationTest {
)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes).captureEvent(any(), anyOrNull())
// we do not call markFlushed, hence it should time out waiting for flush, but because
// we drop the event, it should not even come to this if-check
verify(fixture.logger, never()).log(
@@ -360,9 +360,9 @@ class AnrV2IntegrationTest {
fixture.addAppExitInfo(timestamp = newTimestamp - 1 * 60 * 1000)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, times(2)).captureEvent(
+ verify(fixture.scopes, times(2)).captureEvent(
any(),
argThat {
val hint = HintUtils.getSentrySdkHint(this)
@@ -382,10 +382,10 @@ class AnrV2IntegrationTest {
fixture.addAppExitInfo(timestamp = newTimestamp - 1 * 60 * 1000)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
// only the latest anr is reported which should be enrichable
- verify(fixture.hub, atMost(1)).captureEvent(
+ verify(fixture.scopes, atMost(1)).captureEvent(
any(),
argThat {
val hint = HintUtils.getSentrySdkHint(this)
@@ -402,20 +402,20 @@ class AnrV2IntegrationTest {
fixture.addAppExitInfo(timestamp = newTimestamp - TimeUnit.DAYS.toMillis(1))
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
// the order is reverse here, so the oldest ANR will be reported first to keep track of
// last reported ANR in a marker file
- inOrder(fixture.hub) {
- verify(fixture.hub).captureEvent(
+ inOrder(fixture.scopes) {
+ verify(fixture.scopes).captureEvent(
argThat { timestamp.time == newTimestamp - TimeUnit.DAYS.toMillis(2) },
anyOrNull()
)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
argThat { timestamp.time == newTimestamp - TimeUnit.DAYS.toMillis(1) },
anyOrNull()
)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
argThat { timestamp.time == newTimestamp },
anyOrNull()
)
@@ -427,9 +427,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = oldTimestamp)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
any(),
argThat {
val hint = HintUtils.getSentrySdkHint(this)
@@ -443,9 +443,9 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = oldTimestamp)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
any(),
argThat {
val hint = HintUtils.getSentrySdkHint(this)
@@ -472,7 +472,7 @@ class AnrV2IntegrationTest {
)
}
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
// we store envelope with StartSessionHint on different thread after some delay, which
// triggers the previous session flush, so no timeout
@@ -493,14 +493,14 @@ class AnrV2IntegrationTest {
)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
verify(fixture.logger, never()).log(
any(),
argThat { startsWith("Timed out waiting to flush previous session to its own file.") },
any()
)
- verify(fixture.hub).captureEvent(any(), any())
+ verify(fixture.scopes).captureEvent(any(), any())
}
@Test
@@ -512,7 +512,7 @@ class AnrV2IntegrationTest {
)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
verify(fixture.logger).log(
any(),
@@ -532,9 +532,9 @@ class AnrV2IntegrationTest {
)
fixture.addAppExitInfo(timestamp = newTimestamp)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub).captureEvent(
+ verify(fixture.scopes).captureEvent(
any(),
check {
assertNotNull(it.threadDump)
@@ -547,8 +547,8 @@ class AnrV2IntegrationTest {
val integration = fixture.getSut(tmpDir, lastReportedAnrTimestamp = oldTimestamp)
fixture.addAppExitInfo(timestamp = newTimestamp, addTrace = false)
- integration.register(fixture.hub, fixture.options)
+ integration.register(fixture.scopes, fixture.options)
- verify(fixture.hub, never()).captureEvent(any(), anyOrNull())
+ verify(fixture.scopes, never()).captureEvent(any(), anyOrNull())
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt
index 15a6d690e5..9ae0c1c1c0 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppComponentsBreadcrumbsIntegrationTest.kt
@@ -5,8 +5,9 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Breadcrumb
-import io.sentry.IHub
+import io.sentry.IScopes
import io.sentry.SentryLevel
+import io.sentry.test.ImmediateExecutorService
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
@@ -36,20 +37,24 @@ class AppComponentsBreadcrumbsIntegrationTest {
@Test
fun `When app components breadcrumb is enabled, it registers callback`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
verify(fixture.context).registerComponentCallbacks(any())
}
@Test
fun `When app components breadcrumb is enabled, but ComponentCallbacks is not ready, do not throw`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
whenever(fixture.context.registerComponentCallbacks(any())).thenThrow(NullPointerException())
- sut.register(hub, options)
+ sut.register(scopes, options)
assertFalse(options.isEnableAppComponentBreadcrumbs)
}
@@ -58,18 +63,21 @@ class AppComponentsBreadcrumbsIntegrationTest {
val sut = fixture.getSut()
val options = SentryAndroidOptions().apply {
isEnableAppComponentBreadcrumbs = false
+ executorService = ImmediateExecutorService()
}
- val hub = mock()
- sut.register(hub, options)
+ val scopes = mock()
+ sut.register(scopes, options)
verify(fixture.context, never()).registerComponentCallbacks(any())
}
@Test
fun `When AppComponentsBreadcrumbsIntegrationTest is closed, it should unregister the callback`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
sut.close()
verify(fixture.context).unregisterComponentCallbacks(any())
}
@@ -77,22 +85,26 @@ class AppComponentsBreadcrumbsIntegrationTest {
@Test
fun `When app components breadcrumb is closed, but ComponentCallbacks is not ready, do not throw`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
whenever(fixture.context.registerComponentCallbacks(any())).thenThrow(NullPointerException())
whenever(fixture.context.unregisterComponentCallbacks(any())).thenThrow(NullPointerException())
- sut.register(hub, options)
+ sut.register(scopes, options)
sut.close()
}
@Test
fun `When low memory event, a breadcrumb with type, category and level should be set`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
sut.onLowMemory()
- verify(hub).addBreadcrumb(
+ verify(scopes).addBreadcrumb(
check {
assertEquals("device.event", it.category)
assertEquals("system", it.type)
@@ -104,11 +116,13 @@ class AppComponentsBreadcrumbsIntegrationTest {
@Test
fun `When trim memory event with level, a breadcrumb with type, category and level should be set`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
- verify(hub).addBreadcrumb(
+ verify(scopes).addBreadcrumb(
check {
assertEquals("device.event", it.category)
assertEquals("system", it.type)
@@ -120,21 +134,25 @@ class AppComponentsBreadcrumbsIntegrationTest {
@Test
fun `When trim memory event with level not so high, do not add a breadcrumb`() {
val sut = fixture.getSut()
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
sut.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL)
- verify(hub, never()).addBreadcrumb(any())
+ verify(scopes, never()).addBreadcrumb(any())
}
@Test
fun `When device orientation event, a breadcrumb with type, category and level should be set`() {
val sut = AppComponentsBreadcrumbsIntegration(ApplicationProvider.getApplicationContext())
- val options = SentryAndroidOptions()
- val hub = mock()
- sut.register(hub, options)
+ val options = SentryAndroidOptions().apply {
+ executorService = ImmediateExecutorService()
+ }
+ val scopes = mock()
+ sut.register(scopes, options)
sut.onConfigurationChanged(mock())
- verify(hub).addBreadcrumb(
+ verify(scopes).addBreadcrumb(
check {
assertEquals("device.orientation", it.category)
assertEquals("navigation", it.type)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt
index ed8d53227c..733aefa8d6 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/AppLifecycleIntegrationTest.kt
@@ -2,7 +2,7 @@ package io.sentry.android.core
import android.os.Looper
import androidx.test.ext.junit.runners.AndroidJUnit4
-import io.sentry.IHub
+import io.sentry.IScopes
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -17,7 +17,7 @@ import kotlin.test.assertNull
class AppLifecycleIntegrationTest {
private class Fixture {
- val hub = mock()
+ val scopes = mock()
lateinit var handler: MainLooperHandler
val options = SentryAndroidOptions()
@@ -33,7 +33,7 @@ class AppLifecycleIntegrationTest {
fun `When AppLifecycleIntegration is added, lifecycle watcher should be started`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.watcher)
}
@@ -46,7 +46,7 @@ class AppLifecycleIntegrationTest {
isEnableAutoSessionTracking = false
}
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNull(sut.watcher)
}
@@ -55,7 +55,7 @@ class AppLifecycleIntegrationTest {
fun `When AppLifecycleIntegration is closed, lifecycle watcher should be closed`() {
val sut = fixture.getSut()
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.watcher)
@@ -70,7 +70,7 @@ class AppLifecycleIntegrationTest {
val latch = CountDownLatch(1)
Thread {
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
latch.countDown()
}.start()
@@ -84,7 +84,7 @@ class AppLifecycleIntegrationTest {
val sut = fixture.getSut()
val latch = CountDownLatch(1)
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.watcher)
@@ -103,7 +103,7 @@ class AppLifecycleIntegrationTest {
val sut = fixture.getSut(mockHandler = false)
val latch = CountDownLatch(1)
- sut.register(fixture.hub, fixture.options)
+ sut.register(fixture.scopes, fixture.options)
assertNotNull(sut.watcher)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt
index b758fae1f8..588a32a656 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ContextUtilsTest.kt
@@ -29,6 +29,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
+import kotlin.test.assertSame
import kotlin.test.assertTrue
@Config(sdk = [33])
@@ -213,4 +214,21 @@ class ContextUtilsTest {
)
assertFalse(ContextUtils.isForegroundImportance())
}
+
+ @Test
+ fun `getApplicationContext returns context if app context is null`() {
+ val contextMock = mock()
+ val appContext = ContextUtils.getApplicationContext(contextMock)
+ assertSame(contextMock, appContext)
+ }
+
+ @Test
+ fun `getApplicationContext returns app context`() {
+ val contextMock = mock()
+ val appContextMock = mock()
+ whenever(contextMock.applicationContext).thenReturn(appContextMock)
+
+ val appContext = ContextUtils.getApplicationContext(contextMock)
+ assertSame(appContextMock, appContext)
+ }
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/CurrentActivityIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/CurrentActivityIntegrationTest.kt
index 6330623121..ecdbff5104 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/CurrentActivityIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/CurrentActivityIntegrationTest.kt
@@ -3,7 +3,7 @@ package io.sentry.android.core
import android.app.Activity
import android.app.Application
import androidx.test.ext.junit.runners.AndroidJUnit4
-import io.sentry.IHub
+import io.sentry.IScopes
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -19,7 +19,7 @@ class CurrentActivityIntegrationTest {
private class Fixture {
val application = mock()
val activity = mock()
- val hub = mock()
+ val scopes = mock()
val options = SentryAndroidOptions().apply {
dsn = "https://key@sentry.io/proj"
@@ -27,7 +27,7 @@ class CurrentActivityIntegrationTest {
fun getSut(): CurrentActivityIntegration {
val integration = CurrentActivityIntegration(application)
- integration.register(hub, options)
+ integration.register(scopes, options)
return integration
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt
index 80954f67a5..9aac83ebad 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/DefaultAndroidEventProcessorTest.kt
@@ -7,7 +7,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.DiagnosticLogger
import io.sentry.Hint
-import io.sentry.IHub
+import io.sentry.IScopes
import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryTracer
@@ -62,13 +62,13 @@ class DefaultAndroidEventProcessorTest {
sdkVersion = SdkVersion("test", "1.2.3")
}
- val hub: IHub = mock()
+ val scopes: IScopes = mock()
lateinit var sentryTracer: SentryTracer
fun getSut(context: Context): DefaultAndroidEventProcessor {
- whenever(hub.options).thenReturn(options)
- sentryTracer = SentryTracer(TransactionContext("", ""), hub)
+ whenever(scopes.options).thenReturn(options)
+ sentryTracer = SentryTracer(TransactionContext("", ""), scopes)
return DefaultAndroidEventProcessor(context, buildInfo, options)
}
}
@@ -493,12 +493,11 @@ class DefaultAndroidEventProcessorTest {
}
@Test
- fun `Event sets language and locale`() {
+ fun `Event sets locale`() {
val sut = fixture.getSut(context)
assertNotNull(sut.process(SentryEvent(), Hint())) {
val device = it.contexts.device!!
- assertEquals("en", device.language)
assertEquals("en_US", device.locale)
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt
index 699fa2d2f2..69b2eee2ec 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/EnvelopeFileObserverIntegrationTest.kt
@@ -1,13 +1,13 @@
package io.sentry.android.core
import androidx.test.ext.junit.runners.AndroidJUnit4
-import io.sentry.Hub
-import io.sentry.IHub
import io.sentry.ILogger
+import io.sentry.IScopes
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.test.DeferredExecutorService
import io.sentry.test.ImmediateExecutorService
+import io.sentry.test.createTestScopes
import org.junit.runner.RunWith
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
@@ -24,7 +24,7 @@ import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class EnvelopeFileObserverIntegrationTest {
inner class Fixture {
- val hub: IHub = mock()
+ val scopes: IScopes = mock()
private lateinit var options: SentryAndroidOptions
val logger = mock()
@@ -33,7 +33,7 @@ class EnvelopeFileObserverIntegrationTest {
options.setLogger(logger)
options.isDebug = true
optionConfiguration(options)
- whenever(hub.options).thenReturn(options)
+ whenever(scopes.options).thenReturn(options)
return object : EnvelopeFileObserverIntegration() {
override fun getPath(options: SentryOptions): String? = file.absolutePath
@@ -65,27 +65,25 @@ class EnvelopeFileObserverIntegrationTest {
}
@Test
- fun `when hub is closed, integrations should be closed`() {
+ fun `when scopes is closed, integrations should be closed`() {
val integrationMock = mock()
val options = SentryOptions()
options.dsn = "https://key@sentry.io/proj"
options.cacheDirPath = file.absolutePath
options.addIntegration(integrationMock)
options.setSerializer(mock())
-// val expected = HubAdapter.getInstance()
- val hub = Hub(options)
-// verify(integrationMock).register(expected, options)
- hub.close()
+ val scopes = createTestScopes(options)
+ scopes.close()
verify(integrationMock).close()
}
@Test
- fun `when hub is closed right after start, integration is not registered`() {
+ fun `when scopes is closed right after start, integration is not registered`() {
val deferredExecutorService = DeferredExecutorService()
val integration = fixture.getSut {
it.executorService = deferredExecutorService
}
- integration.register(fixture.hub, fixture.hub.options)
+ integration.register(fixture.scopes, fixture.scopes.options)
integration.close()
deferredExecutorService.runAll()
verify(fixture.logger, never()).log(eq(SentryLevel.DEBUG), eq("EnvelopeFileObserverIntegration installed."))
@@ -96,7 +94,7 @@ class EnvelopeFileObserverIntegrationTest {
val integration = fixture.getSut {
it.executorService = mock()
}
- integration.register(fixture.hub, fixture.hub.options)
+ integration.register(fixture.scopes, fixture.scopes.options)
verify(fixture.logger).log(
eq(SentryLevel.DEBUG),
eq("Registering EnvelopeFileObserverIntegration for path: %s"),
@@ -110,7 +108,7 @@ class EnvelopeFileObserverIntegrationTest {
val integration = fixture.getSut {
it.executorService = ImmediateExecutorService()
}
- integration.register(fixture.hub, fixture.hub.options)
+ integration.register(fixture.scopes, fixture.scopes.options)
verify(fixture.logger).log(
eq(SentryLevel.DEBUG),
eq("Registering EnvelopeFileObserverIntegration for path: %s"),
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt
index 3aa8cb575e..5f63f39cae 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt
@@ -7,9 +7,9 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.Breadcrumb
import io.sentry.Hint
-import io.sentry.Hub
import io.sentry.IScope
import io.sentry.Scope
+import io.sentry.ScopeType
import io.sentry.Sentry
import io.sentry.SentryEnvelope
import io.sentry.SentryEnvelopeHeader
@@ -27,6 +27,7 @@ import io.sentry.protocol.Contexts
import io.sentry.protocol.Mechanism
import io.sentry.protocol.SentryId
import io.sentry.protocol.User
+import io.sentry.test.createTestScopes
import io.sentry.transport.ITransport
import io.sentry.transport.RateLimiter
import org.junit.runner.RunWith
@@ -87,7 +88,7 @@ class InternalSentrySdkTest {
fun captureEnvelopeWithEvent(event: SentryEvent = SentryEvent(), maybeStartNewSession: Boolean = false) {
// create an envelope with session data
- val options = Sentry.getCurrentHub().options
+ val options = Sentry.getCurrentScopes().options
val eventId = SentryId()
val header = SentryEnvelopeHeader(eventId)
val eventItem = SentryEnvelopeItem.fromEvent(options.serializer, event)
@@ -202,40 +203,34 @@ class InternalSentrySdkTest {
@BeforeTest
fun `set up`() {
+ Sentry.close()
context = ApplicationProvider.getApplicationContext()
DeviceInfoUtil.resetInstance()
}
@Test
- fun `current scope returns null when hub is no-op`() {
- Sentry.getCurrentHub().close()
+ fun `current scope returns null when scopes is no-op`() {
+ Sentry.setCurrentScopes(createTestScopes(enabled = false))
val scope = InternalSentrySdk.getCurrentScope()
assertNull(scope)
}
@Test
- fun `current scope returns obj when hub is active`() {
- Sentry.setCurrentHub(
- Hub(
- SentryOptions().apply {
- dsn = "https://key@uri/1234567"
- }
- )
- )
+ fun `current scope returns obj when scopes is active`() {
+ val fixture = Fixture()
+ fixture.init(context)
val scope = InternalSentrySdk.getCurrentScope()
assertNotNull(scope)
}
@Test
fun `current scope returns a copy of the scope`() {
- Sentry.setCurrentHub(
- Hub(
- SentryOptions().apply {
- dsn = "https://key@uri/1234567"
- }
- )
- )
+ val fixture = Fixture()
+ fixture.init(context)
Sentry.addBreadcrumb("test")
+ Sentry.configureScope(ScopeType.CURRENT) { scope -> scope.addBreadcrumb(Breadcrumb("currentBreadcrumb")) }
+ Sentry.configureScope(ScopeType.ISOLATION) { scope -> scope.addBreadcrumb(Breadcrumb("isolationBreadcrumb")) }
+ Sentry.configureScope(ScopeType.GLOBAL) { scope -> scope.addBreadcrumb(Breadcrumb("globalBreadcrumb")) }
// when the clone is modified
val clonedScope = InternalSentrySdk.getCurrentScope()!!
@@ -243,7 +238,7 @@ class InternalSentrySdkTest {
// then modifications should not be reflected
Sentry.configureScope { scope ->
- assertEquals(1, scope.breadcrumbs.size)
+ assertEquals(3, scope.breadcrumbs.size)
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt
index be30993142..93a731481b 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/LifecycleWatcherTest.kt
@@ -3,10 +3,12 @@ package io.sentry.android.core
import androidx.lifecycle.LifecycleOwner
import io.sentry.Breadcrumb
import io.sentry.DateUtils
-import io.sentry.IHub
import io.sentry.IScope
+import io.sentry.IScopes
+import io.sentry.ReplayController
import io.sentry.ScopeCallback
import io.sentry.SentryLevel
+import io.sentry.SentryOptions
import io.sentry.Session
import io.sentry.Session.State
import io.sentry.transport.ICurrentDateProvider
@@ -32,8 +34,10 @@ class LifecycleWatcherTest {
private class Fixture {
val ownerMock = mock()
- val hub = mock()
+ val scopes = mock()
val dateProvider = mock()
+ val options = SentryOptions()
+ val replayController = mock()
fun getSUT(
sessionIntervalMillis: Long = 0L,
@@ -44,12 +48,14 @@ class LifecycleWatcherTest {
val argumentCaptor: ArgumentCaptor = ArgumentCaptor.forClass(ScopeCallback::class.java)
val scope = mock()
whenever(scope.session).thenReturn(session)
- whenever(hub.configureScope(argumentCaptor.capture())).thenAnswer {
+ whenever(scopes.configureScope(argumentCaptor.capture())).thenAnswer {
argumentCaptor.value.run(scope)
}
+ options.setReplayController(replayController)
+ whenever(scopes.options).thenReturn(options)
return LifecycleWatcher(
- hub,
+ scopes,
sessionIntervalMillis,
enableAutoSessionTracking,
enableAppLifecycleBreadcrumbs,
@@ -69,7 +75,8 @@ class LifecycleWatcherTest {
fun `if last started session is 0, start new session`() {
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub).startSession()
+ verify(fixture.scopes).startSession()
+ verify(fixture.replayController).start()
}
@Test
@@ -78,7 +85,8 @@ class LifecycleWatcherTest {
whenever(fixture.dateProvider.currentTimeMillis).thenReturn(1L, 2L)
watcher.onStart(fixture.ownerMock)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub, times(2)).startSession()
+ verify(fixture.scopes, times(2)).startSession()
+ verify(fixture.replayController, times(2)).start()
}
@Test
@@ -87,7 +95,8 @@ class LifecycleWatcherTest {
whenever(fixture.dateProvider.currentTimeMillis).thenReturn(2L, 1L)
watcher.onStart(fixture.ownerMock)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub).startSession()
+ verify(fixture.scopes).startSession()
+ verify(fixture.replayController).start()
}
@Test
@@ -95,7 +104,8 @@ class LifecycleWatcherTest {
val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
watcher.onStop(fixture.ownerMock)
- verify(fixture.hub, timeout(10000)).endSession()
+ verify(fixture.scopes, timeout(10000)).endSession()
+ verify(fixture.replayController, timeout(10000)).stop()
}
@Test
@@ -109,73 +119,29 @@ class LifecycleWatcherTest {
watcher.onStart(fixture.ownerMock)
assertNull(watcher.timerTask)
- verify(fixture.hub, never()).endSession()
+ verify(fixture.scopes, never()).endSession()
+ verify(fixture.replayController, never()).stop()
}
@Test
fun `When session tracking is disabled, do not start session`() {
val watcher = fixture.getSUT(enableAutoSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub, never()).startSession()
+ verify(fixture.scopes, never()).startSession()
}
@Test
fun `When session tracking is disabled, do not end session`() {
val watcher = fixture.getSUT(enableAutoSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStop(fixture.ownerMock)
- assertNull(watcher.timerTask)
- verify(fixture.hub, never()).endSession()
- }
-
- @Test
- fun `When session tracking is enabled, add breadcrumb on start`() {
- val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
- watcher.onStart(fixture.ownerMock)
- verify(fixture.hub).addBreadcrumb(
- check {
- assertEquals("app.lifecycle", it.category)
- assertEquals("session", it.type)
- assertEquals(SentryLevel.INFO, it.level)
- // cant assert data, its not a public API
- }
- )
- }
-
- @Test
- fun `When session tracking is enabled, add breadcrumb on stop`() {
- val watcher = fixture.getSUT(enableAppLifecycleBreadcrumbs = false)
- watcher.onStop(fixture.ownerMock)
- verify(fixture.hub, timeout(10000)).endSession()
- verify(fixture.hub).addBreadcrumb(
- check {
- assertEquals("app.lifecycle", it.category)
- assertEquals("session", it.type)
- assertEquals(SentryLevel.INFO, it.level)
- // cant assert data, its not a public API
- }
- )
- }
-
- @Test
- fun `When session tracking is disabled, do not add breadcrumb on start`() {
- val watcher = fixture.getSUT(enableAutoSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
- watcher.onStart(fixture.ownerMock)
- verify(fixture.hub, never()).addBreadcrumb(any())
- }
-
- @Test
- fun `When session tracking is disabled, do not add breadcrumb on stop`() {
- val watcher = fixture.getSUT(enableAutoSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
- watcher.onStop(fixture.ownerMock)
- assertNull(watcher.timerTask)
- verify(fixture.hub, never()).addBreadcrumb(any())
+ verify(fixture.scopes, never()).endSession()
}
@Test
fun `When app lifecycle breadcrumbs is enabled, add breadcrumb on start`() {
val watcher = fixture.getSUT(enableAutoSessionTracking = false)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub).addBreadcrumb(
+ verify(fixture.scopes).addBreadcrumb(
check {
assertEquals("app.lifecycle", it.category)
assertEquals("navigation", it.type)
@@ -189,14 +155,14 @@ class LifecycleWatcherTest {
fun `When app lifecycle breadcrumbs is disabled, do not add breadcrumb on start`() {
val watcher = fixture.getSUT(enableAutoSessionTracking = false, enableAppLifecycleBreadcrumbs = false)
watcher.onStart(fixture.ownerMock)
- verify(fixture.hub, never()).addBreadcrumb(any