From 43e4cc8d2c8bc8b26f7b32ab2188170e9d605e4d Mon Sep 17 00:00:00 2001 From: YingYing Chen <40571804+YYChen01988@users.noreply.github.com> Date: Wed, 10 Apr 2024 08:39:40 +0100 Subject: [PATCH] Allow early session starts in auto mode (#2006) * feat(session)Allow early session starts in auto mode * feat(session)Allow early session starts in auto mode * feat(session)End to end test * feat(feature flags): don't copy FeatureFlags on the crashing thread * feat(session)Allow early session starts in auto mode --------- Co-authored-by: jason --- CHANGELOG.md | 3 ++ bugsnag-android-core/detekt-baseline.xml | 1 - .../com/bugsnag/android/SessionTracker.java | 22 +++++++++++-- .../android/mazerunner/MazerunnerApp.kt | 1 + .../mazerunner/StartSessionBehaviour.kt | 32 +++++++++++++++++++ .../scenarios/StartSessionAutoModeScenario.kt | 27 ++++++++++++++++ features/smoke_tests/03_sessions.feature | 5 +++ 7 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartSessionBehaviour.kt create mode 100644 features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartSessionAutoModeScenario.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c081e0398..a71f93267c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ * FeatureFlags are now a copy-on-write structure, and so don't need to be defensive copied on a crashing thread [#2005](https://github.com/bugsnag/bugsnag-android/pull/2005) +* Allow `Bugsnag.startSession` to be called with automatic session tracking, and not have the first manual session be over written by the first automatic session. + [#2006](https://github.com/bugsnag/bugsnag-android/pull/2006) + ## 6.3.0 (2024-03-19) ### Enhancements diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 1435111433..1e44fd786f 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -13,7 +13,6 @@ LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array<String>?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ runtimeVersions: MutableMap<String, Any>? ) LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array<String>? ) LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val internalDeviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger ) - LongParameterList:DeviceIdStore.kt$DeviceIdStore$( context: Context, deviceIdfile: File = File(context.filesDir, "device-id"), deviceIdGenerator: () -> UUID = { UUID.randomUUID() }, internalDeviceIdfile: File = File(context.filesDir, "internal-device-id"), internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() }, private val sharedPrefMigrator: SharedPrefMigrator, logger: Logger ) LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap<String, Any>, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? ) LongParameterList:EventFilenameInfo.kt$EventFilenameInfo.Companion$( obj: Any, uuid: String = UUID.randomUUID().toString(), apiKey: String?, timestamp: Long = System.currentTimeMillis(), config: ImmutableConfig, isLaunching: Boolean? = null ) LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList<Breadcrumb> = mutableListOf(), discardClasses: Set<Pattern> = setOf(), errors: MutableList<Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection<String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList<Thread> = mutableListOf(), user: User = User(), redactionKeys: Set<Pattern>? = null ) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java index bb72ddc5dd..e9e15b2a2e 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionTracker.java @@ -36,6 +36,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi private volatile Session currentSession = null; final BackgroundTaskService backgroundTaskService; final Logger logger; + private boolean shouldSuppressFirstAutoSession = false; SessionTracker(ImmutableConfig configuration, CallbackState callbackState, @@ -76,7 +77,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi @VisibleForTesting Session startNewSession(@NonNull Date date, @Nullable User user, boolean autoCaptured) { - if (client.getConfig().shouldDiscardSession(autoCaptured)) { + if (shouldDiscardSession(autoCaptured)) { return null; } String id = UUID.randomUUID().toString(); @@ -92,12 +93,29 @@ Session startNewSession(@NonNull Date date, @Nullable User user, } Session startSession(boolean autoCaptured) { - if (client.getConfig().shouldDiscardSession(autoCaptured)) { + if (shouldDiscardSession(autoCaptured)) { return null; } return startNewSession(new Date(), client.getUser(), autoCaptured); } + private boolean shouldDiscardSession(boolean autoCaptured) { + if (client.getConfig().shouldDiscardSession(autoCaptured)) { + return true; + } else { + Session existingSession = currentSession; + if (autoCaptured + && existingSession != null + && !existingSession.isAutoCaptured() + && shouldSuppressFirstAutoSession) { + shouldSuppressFirstAutoSession = true; + return true; + } + } + return false; + } + + void pauseSession() { Session session = currentSession; diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt index d8788d1a60..ad586c4f5d 100644 --- a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MazerunnerApp.kt @@ -10,6 +10,7 @@ class MazerunnerApp : Application() { super.onCreate() triggerStartupAnrIfRequired() setupNonSdkUsageStrictMode() + triggerManualSessionIfRequired() } /** diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartSessionBehaviour.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartSessionBehaviour.kt new file mode 100644 index 0000000000..b0c0ef4d07 --- /dev/null +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartSessionBehaviour.kt @@ -0,0 +1,32 @@ +package com.bugsnag.android.mazerunner + +import android.app.Application +import com.bugsnag.android.Bugsnag +import com.bugsnag.android.Configuration +import com.bugsnag.android.EndpointConfiguration + +fun Application.triggerManualSessionIfRequired() { + val prefs = getSharedPreferences("SessionPreferences", android.content.Context.MODE_PRIVATE) + val manualSession = prefs.getBoolean("manualSession", false) + + if (manualSession) { + val notifyEndpoint = prefs.getString("notify", null) + val sessionsEndpoint = prefs.getString("sessions", null) + + // we remove the preferences so that we don't affect any future startup + prefs.edit() + .remove("notify") + .remove("sessions") + .remove("manualSession") + .commit() + + // we have to startup Bugsnag at this point + val config = Configuration.load(this) + if (!notifyEndpoint.isNullOrBlank() && !sessionsEndpoint.isNullOrBlank()) { + config.endpoints = EndpointConfiguration(notifyEndpoint, sessionsEndpoint) + } + + Bugsnag.start(this, config) + Bugsnag.startSession() + } +} diff --git a/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartSessionAutoModeScenario.kt b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartSessionAutoModeScenario.kt new file mode 100644 index 0000000000..f157fbdac8 --- /dev/null +++ b/features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/StartSessionAutoModeScenario.kt @@ -0,0 +1,27 @@ +package com.bugsnag.android.mazerunner.scenarios + +import android.content.Context +import com.bugsnag.android.Configuration +import kotlin.system.exitProcess + +internal class StartSessionAutoModeScenario( + config: Configuration, + context: Context, + eventMetadata: String? +) : Scenario(config, context, eventMetadata) { + override fun startScenario() { + context.applicationContext + .getSharedPreferences("SessionPreferences", Context.MODE_PRIVATE) + .edit() + .putBoolean("manualSession", MANUAL_START) + .putString("notify", config.endpoints.notify) + .putString("sessions", config.endpoints.sessions) + .commit() + + exitProcess(0) + } + + companion object { + private const val MANUAL_START = true + } +} diff --git a/features/smoke_tests/03_sessions.feature b/features/smoke_tests/03_sessions.feature index 54a9701f5f..3033fed8bf 100644 --- a/features/smoke_tests/03_sessions.feature +++ b/features/smoke_tests/03_sessions.feature @@ -112,3 +112,8 @@ Feature: Session functionality smoke tests And the event "user.email" equals "ABC.CBA.CA" And the event "user.name" equals "ManualSessionSmokeScenario" + Scenario: Start session in auto mode + When I clear any error dialogue + And I run "StartSessionAutoModeScenario" + And I relaunch the app after a crash + Then I wait to receive a session \ No newline at end of file