From 1de23304fef4ad017b105e3c89466eebf606b383 Mon Sep 17 00:00:00 2001
From: Markus Hintersteiner <markus.hintersteiner@sentry.io>
Date: Mon, 27 Nov 2023 13:30:17 +0100
Subject: [PATCH 1/2] Extract Activity Breadcrumbs into own integration

---
 .../api/sentry-android-core.api               |  13 ++
 .../core/ActivityBreadcrumbsIntegration.java  | 124 +++++++++++++
 .../core/ActivityLifecycleIntegration.java    |  37 +---
 .../core/AndroidOptionsInitializer.java       |   1 +
 .../ActivityBreadcrumbsIntegrationTest.kt     | 157 ++++++++++++++++
 .../core/ActivityLifecycleIntegrationTest.kt  | 173 +-----------------
 .../core/AndroidOptionsInitializerTest.kt     |   9 +
 .../sentry/android/core/SentryAndroidTest.kt  |   3 +-
 8 files changed, 312 insertions(+), 205 deletions(-)
 create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java
 create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt

diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api
index f5511b996c7..8e5ea2abab0 100644
--- a/sentry-android-core/api/sentry-android-core.api
+++ b/sentry-android-core/api/sentry-android-core.api
@@ -1,3 +1,16 @@
+public final class io/sentry/android/core/ActivityBreadcrumbsIntegration : android/app/Application$ActivityLifecycleCallbacks, io/sentry/Integration, java/io/Closeable {
+	public fun <init> (Landroid/app/Application;)V
+	public fun close ()V
+	public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
+	public fun onActivityDestroyed (Landroid/app/Activity;)V
+	public fun onActivityPaused (Landroid/app/Activity;)V
+	public fun onActivityResumed (Landroid/app/Activity;)V
+	public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
+	public fun onActivityStarted (Landroid/app/Activity;)V
+	public fun onActivityStopped (Landroid/app/Activity;)V
+	public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
+}
+
 public final class io/sentry/android/core/ActivityFramesTracker {
 	public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/android/core/SentryAndroidOptions;)V
 	public fun <init> (Lio/sentry/android/core/LoadClass;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/MainLooperHandler;)V
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java
new file mode 100644
index 00000000000..86cffc153e8
--- /dev/null
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityBreadcrumbsIntegration.java
@@ -0,0 +1,124 @@
+package io.sentry.android.core;
+
+import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+import io.sentry.Breadcrumb;
+import io.sentry.Hint;
+import io.sentry.IHub;
+import io.sentry.Integration;
+import io.sentry.SentryLevel;
+import io.sentry.SentryOptions;
+import io.sentry.util.Objects;
+import java.io.Closeable;
+import java.io.IOException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/** Automatically adds breadcrumbs for Activity Lifecycle Events. */
+public final class ActivityBreadcrumbsIntegration
+    implements Integration, Closeable, Application.ActivityLifecycleCallbacks {
+
+  private final @NotNull Application application;
+  private @Nullable IHub hub;
+  private boolean enabled;
+
+  public ActivityBreadcrumbsIntegration(final @NotNull Application application) {
+    this.application = Objects.requireNonNull(application, "Application is required");
+  }
+
+  @Override
+  public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
+    final SentryAndroidOptions androidOptions =
+        Objects.requireNonNull(
+            (options instanceof SentryAndroidOptions) ? (SentryAndroidOptions) options : null,
+            "SentryAndroidOptions is required");
+
+    this.hub = Objects.requireNonNull(hub, "Hub is required");
+    this.enabled = androidOptions.isEnableActivityLifecycleBreadcrumbs();
+    options
+        .getLogger()
+        .log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration enabled: %s", enabled);
+
+    if (enabled) {
+      application.registerActivityLifecycleCallbacks(this);
+      options.getLogger().log(SentryLevel.DEBUG, "ActivityBreadcrumbIntegration installed.");
+      addIntegrationToSdkVersion();
+    }
+  }
+
+  @Override
+  public void close() throws IOException {
+    if (enabled) {
+      application.unregisterActivityLifecycleCallbacks(this);
+      if (hub != null) {
+        hub.getOptions()
+            .getLogger()
+            .log(SentryLevel.DEBUG, "ActivityBreadcrumbsIntegration removed.");
+      }
+    }
+  }
+
+  @Override
+  public synchronized void onActivityCreated(
+      final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
+    addBreadcrumb(activity, "created");
+  }
+
+  @Override
+  public synchronized void onActivityStarted(final @NotNull Activity activity) {
+    addBreadcrumb(activity, "started");
+  }
+
+  @SuppressLint("NewApi")
+  @Override
+  public synchronized void onActivityResumed(final @NotNull Activity activity) {
+    addBreadcrumb(activity, "resumed");
+  }
+
+  @Override
+  public synchronized void onActivityPaused(final @NotNull Activity activity) {
+    addBreadcrumb(activity, "paused");
+  }
+
+  @Override
+  public synchronized void onActivityStopped(final @NotNull Activity activity) {
+    addBreadcrumb(activity, "stopped");
+  }
+
+  @Override
+  public synchronized void onActivitySaveInstanceState(
+      final @NotNull Activity activity, final @NotNull Bundle outState) {
+    addBreadcrumb(activity, "saveInstanceState");
+  }
+
+  @Override
+  public synchronized void onActivityDestroyed(final @NotNull Activity activity) {
+    addBreadcrumb(activity, "destroyed");
+  }
+
+  private void addBreadcrumb(final @NotNull Activity activity, final @NotNull String state) {
+    if (hub == null) {
+      return;
+    }
+
+    final Breadcrumb breadcrumb = new Breadcrumb();
+    breadcrumb.setType("navigation");
+    breadcrumb.setData("state", state);
+    breadcrumb.setData("screen", getActivityName(activity));
+    breadcrumb.setCategory("ui.lifecycle");
+    breadcrumb.setLevel(SentryLevel.INFO);
+
+    final Hint hint = new Hint();
+    hint.set(ANDROID_ACTIVITY, activity);
+
+    hub.addBreadcrumb(breadcrumb, hint);
+  }
+
+  private @NotNull String getActivityName(final @NotNull Activity activity) {
+    return activity.getClass().getSimpleName();
+  }
+}
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java
index c7ed4ee9b75..429bcdd5527 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java
@@ -1,7 +1,6 @@
 package io.sentry.android.core;
 
 import static io.sentry.MeasurementUnit.Duration.MILLISECOND;
-import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY;
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
@@ -12,9 +11,7 @@
 import android.os.Looper;
 import android.view.View;
 import androidx.annotation.NonNull;
-import io.sentry.Breadcrumb;
 import io.sentry.FullyDisplayedReporter;
-import io.sentry.Hint;
 import io.sentry.IHub;
 import io.sentry.ISpan;
 import io.sentry.ITransaction;
@@ -112,13 +109,6 @@ public void register(final @NotNull IHub hub, final @NotNull SentryOptions optio
 
     this.hub = Objects.requireNonNull(hub, "Hub is required");
 
-    this.options
-        .getLogger()
-        .log(
-            SentryLevel.DEBUG,
-            "ActivityLifecycleIntegration enabled: %s",
-            this.options.isEnableActivityLifecycleBreadcrumbs());
-
     performanceEnabled = isPerformanceEnabled(this.options);
     fullyDisplayedReporter = this.options.getFullyDisplayedReporter();
     timeToFullDisplaySpanEnabled = this.options.isEnableTimeToFullDisplayTracing();
@@ -143,22 +133,6 @@ public void close() throws IOException {
     activityFramesTracker.stop();
   }
 
-  private void addBreadcrumb(final @NotNull Activity activity, final @NotNull String state) {
-    if (options != null && hub != null && options.isEnableActivityLifecycleBreadcrumbs()) {
-      final Breadcrumb breadcrumb = new Breadcrumb();
-      breadcrumb.setType("navigation");
-      breadcrumb.setData("state", state);
-      breadcrumb.setData("screen", getActivityName(activity));
-      breadcrumb.setCategory("ui.lifecycle");
-      breadcrumb.setLevel(SentryLevel.INFO);
-
-      final Hint hint = new Hint();
-      hint.set(ANDROID_ACTIVITY, activity);
-
-      hub.addBreadcrumb(breadcrumb, hint);
-    }
-  }
-
   private @NotNull String getActivityName(final @NotNull Activity activity) {
     return activity.getClass().getSimpleName();
   }
@@ -381,7 +355,6 @@ private void finishTransaction(
   public synchronized void onActivityCreated(
       final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
     setColdStart(savedInstanceState);
-    addBreadcrumb(activity, "created");
     if (hub != null) {
       final @Nullable String activityClassName = ClassUtil.getClassName(activity);
       hub.configureScope(scope -> scope.setScreen(activityClassName));
@@ -407,7 +380,6 @@ public synchronized void onActivityStarted(final @NotNull Activity activity) {
       // working. Moving this to onActivityStarted fixes the problem.
       activityFramesTracker.addActivity(activity);
     }
-    addBreadcrumb(activity, "started");
   }
 
   @SuppressLint("NewApi")
@@ -438,7 +410,6 @@ public synchronized void onActivityResumed(final @NotNull Activity activity) {
         mainHandler.post(() -> onFirstFrameDrawn(ttfdSpan, ttidSpan));
       }
     }
-    addBreadcrumb(activity, "resumed");
   }
 
   @Override
@@ -468,24 +439,22 @@ public synchronized void onActivityPaused(final @NotNull Activity activity) {
         lastPausedTime = hub.getOptions().getDateProvider().now();
       }
     }
-    addBreadcrumb(activity, "paused");
   }
 
   @Override
   public synchronized void onActivityStopped(final @NotNull Activity activity) {
-    addBreadcrumb(activity, "stopped");
+    // no-op
   }
 
   @Override
   public synchronized void onActivitySaveInstanceState(
       final @NotNull Activity activity, final @NotNull Bundle outState) {
-    addBreadcrumb(activity, "saveInstanceState");
+    // no-op
   }
 
   @Override
   public synchronized void onActivityDestroyed(final @NotNull Activity activity) {
-    if (performanceEnabled || options.isEnableActivityLifecycleBreadcrumbs()) {
-      addBreadcrumb(activity, "destroyed");
+    if (performanceEnabled) {
 
       // in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid
       // memory leak
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java
index 5a0fbef9b24..1b13e39cb47 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java
@@ -236,6 +236,7 @@ static void installDefaultIntegrations(
       options.addIntegration(
           new ActivityLifecycleIntegration(
               (Application) context, buildInfoProvider, activityFramesTracker));
+      options.addIntegration(new ActivityBreadcrumbsIntegration((Application) context));
       options.addIntegration(new CurrentActivityIntegration((Application) context));
       options.addIntegration(new UserInteractionIntegration((Application) context, loadClass));
       if (isFragmentAvailable) {
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
new file mode 100644
index 00000000000..f104acebfac
--- /dev/null
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityBreadcrumbsIntegrationTest.kt
@@ -0,0 +1,157 @@
+package io.sentry.android.core
+
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.sentry.Breadcrumb
+import io.sentry.Hub
+import io.sentry.SentryLevel
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.check
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+class ActivityBreadcrumbsIntegrationTest {
+
+    private class Fixture {
+        val application = mock<Application>()
+        val hub = mock<Hub>()
+        val options = SentryAndroidOptions().apply {
+            dsn = "https://key@sentry.io/proj"
+        }
+        val bundle = mock<Bundle>()
+
+        fun getSut(enabled: Boolean = true): ActivityBreadcrumbsIntegration {
+            options.isEnableActivityLifecycleBreadcrumbs = enabled
+            whenever(hub.options).thenReturn(options)
+            return ActivityBreadcrumbsIntegration(
+                application
+            )
+        }
+    }
+
+    private val fixture = Fixture()
+
+    @Test
+    fun `When ActivityBreadcrumbsIntegration is disabled, it should not register the activity callback`() {
+        val sut = fixture.getSut(false)
+        sut.register(fixture.hub, fixture.options)
+
+        verify(fixture.application, never()).registerActivityLifecycleCallbacks(any())
+    }
+
+    @Test
+    fun `When ActivityBreadcrumbsIntegration is enabled, it should register the activity callback`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        verify(fixture.application).registerActivityLifecycleCallbacks(any())
+
+        sut.close()
+        verify(fixture.application).unregisterActivityLifecycleCallbacks(any())
+    }
+
+    @Test
+    fun `When breadcrumb is added, type and category should be set`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityCreated(activity, fixture.bundle)
+
+        verify(fixture.hub).addBreadcrumb(
+            check<Breadcrumb> {
+                assertEquals("ui.lifecycle", it.category)
+                assertEquals("navigation", it.type)
+                assertEquals(SentryLevel.INFO, it.level)
+                // cant assert data, its not a public API
+            },
+            anyOrNull()
+        )
+    }
+
+    @Test
+    fun `When activity is created, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityCreated(activity, fixture.bundle)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is started, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityStarted(activity)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is resumed, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityResumed(activity)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is paused, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityPaused(activity)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is stopped, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityStopped(activity)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is save instance, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivitySaveInstanceState(activity, fixture.bundle)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+
+    @Test
+    fun `When activity is destroyed, it should add a breadcrumb`() {
+        val sut = fixture.getSut()
+        sut.register(fixture.hub, fixture.options)
+
+        val activity = mock<Activity>()
+        sut.onActivityDestroyed(activity)
+
+        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
+    }
+}
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 69113a85b80..73b85fc2ccd 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
@@ -10,7 +10,6 @@ import android.view.View
 import android.view.ViewTreeObserver
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import io.sentry.Breadcrumb
 import io.sentry.DateUtils
 import io.sentry.FullyDisplayedReporter
 import io.sentry.Hub
@@ -19,9 +18,7 @@ import io.sentry.Scope
 import io.sentry.ScopeCallback
 import io.sentry.Sentry
 import io.sentry.SentryDate
-import io.sentry.SentryLevel
 import io.sentry.SentryNanotimeDate
-import io.sentry.SentryOptions
 import io.sentry.SentryTracer
 import io.sentry.Span
 import io.sentry.SpanStatus
@@ -140,7 +137,7 @@ class ActivityLifecycleIntegrationTest {
     }
 
     @Test
-    fun `When activity lifecycle breadcrumb is enabled, it registers callback`() {
+    fun `When ActivityLifecycleIntegration is registered, it registers activity callback`() {
         val sut = fixture.getSut()
         sut.register(fixture.hub, fixture.options)
 
@@ -148,75 +145,7 @@ class ActivityLifecycleIntegrationTest {
     }
 
     @Test
-    fun `When activity lifecycle breadcrumb and tracing are disabled, it still registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When activity lifecycle breadcrumb is disabled but tracing is enabled, it registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-        fixture.options.enableTracing = true
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When activity lifecycle breadcrumb is disabled and tracesSampleRate is set but tracing is disabled, it still registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-        fixture.options.tracesSampleRate = 1.0
-        fixture.options.enableTracing = false
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When activity lifecycle breadcrumb is disabled but tracing sample rate is enabled, it registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-        fixture.options.tracesSampleRate = 1.0
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When activity lifecycle breadcrumb is disabled but tracing sample callback is enabled, it registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-        fixture.options.tracesSampler = SentryOptions.TracesSamplerCallback { 1.0 }
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When activity lifecycle breadcrumb and tracing activity flag are disabled, its still registers callback`() {
-        val sut = fixture.getSut()
-        fixture.options.isEnableActivityLifecycleBreadcrumbs = false
-        fixture.options.tracesSampleRate = 1.0
-        fixture.options.tracesSampler = SentryOptions.TracesSamplerCallback { 1.0 }
-        fixture.options.isEnableAutoActivityLifecycleTracing = false
-
-        sut.register(fixture.hub, fixture.options)
-
-        verify(fixture.application).registerActivityLifecycleCallbacks(any())
-    }
-
-    @Test
-    fun `When ActivityBreadcrumbsIntegration is closed, it should unregister the callback`() {
+    fun `When ActivityLifecycleIntegration is closed, it should unregister the callback`() {
         val sut = fixture.getSut()
         sut.register(fixture.hub, fixture.options)
 
@@ -226,7 +155,7 @@ class ActivityLifecycleIntegrationTest {
     }
 
     @Test
-    fun `When ActivityBreadcrumbsIntegration is closed, it should close the ActivityFramesTracker`() {
+    fun `When ActivityLifecycleIntegration is closed, it should close the ActivityFramesTracker`() {
         val sut = fixture.getSut()
         sut.register(fixture.hub, fixture.options)
 
@@ -235,102 +164,6 @@ class ActivityLifecycleIntegrationTest {
         verify(fixture.activityFramesTracker).stop()
     }
 
-    @Test
-    fun `When breadcrumb is added, type and category should be set`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityCreated(activity, fixture.bundle)
-
-        verify(fixture.hub).addBreadcrumb(
-            check<Breadcrumb> {
-                assertEquals("ui.lifecycle", it.category)
-                assertEquals("navigation", it.type)
-                assertEquals(SentryLevel.INFO, it.level)
-                // cant assert data, its not a public API
-            },
-            anyOrNull()
-        )
-    }
-
-    @Test
-    fun `When activity is created, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityCreated(activity, fixture.bundle)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is started, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityStarted(activity)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is resumed, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityResumed(activity)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is paused, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityPaused(activity)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is stopped, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityStopped(activity)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is save instance, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivitySaveInstanceState(activity, fixture.bundle)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
-    @Test
-    fun `When activity is destroyed, it should add a breadcrumb`() {
-        val sut = fixture.getSut()
-        sut.register(fixture.hub, fixture.options)
-
-        val activity = mock<Activity>()
-        sut.onActivityDestroyed(activity)
-
-        verify(fixture.hub).addBreadcrumb(any<Breadcrumb>(), anyOrNull())
-    }
-
     @Test
     fun `When tracing is disabled, do not start tracing`() {
         val sut = fixture.getSut()
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 7f48dcd7ef8..940501af3a4 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
@@ -411,6 +411,15 @@ class AndroidOptionsInitializerTest {
         assertNull(actual)
     }
 
+    @Test
+    fun `When given Context is not an Application class, do not add ActivityBreadcrumbsIntegration`() {
+        fixture.initSut(hasAppContext = false)
+
+        val actual = fixture.sentryOptions.integrations
+            .firstOrNull { it is ActivityBreadcrumbsIntegration }
+        assertNull(actual)
+    }
+
     @Test
     fun `When given Context is not an Application class, do not add UserInteractionIntegration`() {
         fixture.initSut(hasAppContext = false)
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
index 2a9e20be299..74dca29d113 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
@@ -405,7 +405,7 @@ class SentryAndroidTest {
         fixture.initSut(context = mock<Application>()) { options ->
             optionsRef = options
             options.dsn = "https://key@sentry.io/123"
-            assertEquals(18, options.integrations.size)
+            assertEquals(19, options.integrations.size)
             options.integrations.removeAll {
                 it is UncaughtExceptionHandlerIntegration ||
                     it is ShutdownHookIntegration ||
@@ -415,6 +415,7 @@ class SentryAndroidTest {
                     it is AppLifecycleIntegration ||
                     it is AnrIntegration ||
                     it is ActivityLifecycleIntegration ||
+                    it is ActivityBreadcrumbsIntegration ||
                     it is CurrentActivityIntegration ||
                     it is UserInteractionIntegration ||
                     it is FragmentLifecycleIntegration ||

From 0c2d3d3897919f2e8baadfa46459c50dda661563 Mon Sep 17 00:00:00 2001
From: Markus Hintersteiner <markus.hintersteiner@sentry.io>
Date: Mon, 27 Nov 2023 15:06:55 +0100
Subject: [PATCH 2/2] Update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6250561b51..6012350cc32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### Features
 
 - (Internal, Experimental) Attach spans for Application, ContentProvider, and Activities to app-start ([#3057](https://github.com/getsentry/sentry-java/pull/3057))
+- (Internal) Extract Activity Breadcrumbs generation into own Integration ([#3064](https://github.com/getsentry/sentry-java/pull/3064))
 
 ## 6.34.0