diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 6d78438000..2e96402a4a 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -64,7 +64,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 #v3.28.16
+ uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -83,6 +83,6 @@ jobs:
./gradlew --no-daemon assemble
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 #v3.28.16
+ uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b #v3.29.2
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 4cf61a27bc..9487828c60 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -37,7 +37,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
- uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
+ uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
@@ -68,7 +68,7 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
+ uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif
@@ -77,4 +77,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
+ - uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1e9795c56..12ac086608 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 6.15.0 (2025-07-02)
+
+### Enhancements
+
+* `bugsnag-plugin-android-ndk` no longer exports the `Java_` JNI functions, and instead uses the JNI `RegisterNatives` API to register the native methods.
+ [#2205](https://github.com/bugsnag/bugsnag-android/pull/2205)
+
+### Bug fixes
+
+* The `bugsnag-plugin-android-exitinfo` plugin now calls `setProcessState` (if configured) on a background thread and swallows any rate-limiting errors, so that it does not block the main thread during startup
+ [#2197](https://github.com/bugsnag/bugsnag-android/pull/2197)
+* Improve the scoping of the build-id capturing in `bugsnag-plugin-android-ndk` to more reliably capture the build-id from the correct `.so` file
+ [#2203](https://github.com/bugsnag/bugsnag-android/pull/2203)
+* Fixed a background ANR that could occur during startup if processes do not launch or run quickly enough
+ [#2202](https://github.com/bugsnag/bugsnag-android/pull/2202)
+
## 6.14.0 (2025-06-04)
### Enhancements
diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml
index 843019fbd7..ac410321aa 100644
--- a/bugsnag-android-core/detekt-baseline.xml
+++ b/bugsnag-android-core/detekt-baseline.xml
@@ -6,7 +6,7 @@
CyclomaticComplexMethod:ConfigInternal.kt$ConfigInternal$fun getConfigDifferences(): Map<String, Any>
ImplicitDefaultLocale:Deliverable.kt$Deliverable$String.format("%02x", byte)
LongParameterList:App.kt$App$( /** * The architecture of the running application binary */ var binaryArch: String?, /** * The package name of the application */ var id: String?, /** * The release stage set in [Configuration.releaseStage] */ var releaseStage: String?, /** * The version of the application set in [Configuration.version] */ var version: String?, /** The revision ID from the manifest (React Native apps only) */ var codeBundleId: String?, /** * The unique identifier for the build of the application set in [Configuration.buildUuid] */ buildUuid: Provider<String?>?, /** * The application type set in [Configuration#version] */ var type: String?, /** * The version code of the application set in [Configuration.versionCode] */ var versionCode: Number? )
- LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState )
+ LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: Provider<SessionTracker>, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState )
LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: Provider<String?>?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? )
LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ isLaunching: Boolean? )
LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )
@@ -61,6 +61,7 @@
SwallowedException:ForegroundDetector.kt$ForegroundDetector$e: Exception
SwallowedException:JsonHelperTest.kt$JsonHelperTest$e: IllegalArgumentException
SwallowedException:PluginClient.kt$PluginClient$exc: ClassNotFoundException
+ SwallowedException:RootDetector.kt$RootDetector$ex: IllegalThreadStateException
SwallowedException:SharedPrefMigrator.kt$SharedPrefMigrator$e: RuntimeException
ThrowsCount:JsonHelper.kt$JsonHelper$fun jsonToLong(value: Any?): Long?
TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware
diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt
index d57317c881..18f052ea8f 100644
--- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt
+++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt
@@ -2,6 +2,7 @@ package com.bugsnag.android
import android.content.Context
import android.os.SystemClock
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -41,7 +42,7 @@ class AppDataCollectorForegroundTest {
appContext,
null,
config,
- sessionTracker,
+ ValueProvider(sessionTracker),
null,
launchCrashTracker,
memoryTrimState
diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt
index 09da647a6a..4207d15ed9 100644
--- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt
+++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/AppDataCollectorTest.kt
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager
import android.os.Build
import android.os.Process
import androidx.test.core.app.ApplicationProvider
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -49,7 +50,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -70,7 +71,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -93,7 +94,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -115,7 +116,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -144,7 +145,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -171,7 +172,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
@@ -198,7 +199,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
- client.sessionTracker,
+ ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt
index 6c4733ac54..3363cf5b21 100644
--- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt
+++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/FileStoreTest.kt
@@ -2,6 +2,7 @@ package com.bugsnag.android
import android.app.Application
import androidx.test.core.app.ApplicationProvider
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,6 +49,6 @@ internal class CustomFileStore(
folder: File,
maxStoreCount: Int,
delegate: Delegate?
-) : FileStore(folder, maxStoreCount, NoopLogger, delegate) {
+) : FileStore(folder, maxStoreCount, NoopLogger, ValueProvider(delegate)) {
override fun getFilename(obj: Any?) = "foo.json"
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt
index c1eb409bc8..0347f2a428 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/AppDataCollector.kt
@@ -24,6 +24,7 @@ import android.os.Build.VERSION_CODES
import android.os.Process
import android.os.SystemClock
import com.bugsnag.android.internal.ImmutableConfig
+import com.bugsnag.android.internal.dag.Provider
/**
* Collects various data on the application state
@@ -32,7 +33,7 @@ internal class AppDataCollector(
appContext: Context,
private val packageManager: PackageManager?,
private val config: ImmutableConfig,
- private val sessionTracker: SessionTracker,
+ private val sessionTracker: Provider,
private val activityManager: ActivityManager?,
private val launchCrashTracker: LaunchCrashTracker,
private val memoryTrimState: MemoryTrimState
@@ -54,7 +55,7 @@ internal class AppDataCollector(
App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId)
fun generateAppWithState(): AppWithState {
- val inForeground = sessionTracker.isInForeground
+ val inForeground = sessionTracker.get().isInForeground
val durationInForeground = calculateDurationInForeground(inForeground)
return AppWithState(
@@ -118,7 +119,7 @@ internal class AppDataCollector(
fun getAppDataMetadata(): MutableMap {
val map = HashMap()
map["name"] = appName
- map["activeScreen"] = sessionTracker.contextActivity
+ map["activeScreen"] = sessionTracker.get().contextActivity
map["lowMemory"] = memoryTrimState.isLowMemory
map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription
map["processImportance"] = getProcessImportance()
@@ -168,7 +169,7 @@ internal class AppDataCollector(
*
* @return the duration in ms
*/
- internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.isInForeground): Long? {
+ internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.get().isInForeground): Long? {
if (inForeground == null) {
return null
}
@@ -176,7 +177,7 @@ internal class AppDataCollector(
val nowMs = SystemClock.elapsedRealtime()
var durationMs: Long = 0
- val sessionStartTimeMs: Long = sessionTracker.lastEnteredForegroundMs
+ val sessionStartTimeMs: Long = sessionTracker.get().lastEnteredForegroundMs
if (inForeground && sessionStartTimeMs != 0L) {
durationMs = nowMs - sessionStartTimeMs
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt
index e8967de359..4b9edee875 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt
@@ -34,7 +34,7 @@ internal class DataCollectionModule(
ctx,
ctx.packageManager,
cfg,
- trackerModule.sessionTracker.get(),
+ trackerModule.sessionTracker,
systemServiceModule.activityManager,
trackerModule.launchCrashTracker,
memoryTrimState
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt
index 2d519eb5b1..09996e232a 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt
@@ -29,9 +29,9 @@ internal class EventStorageModule(
cfg.logger,
cfg,
systemServiceModule.storageManager,
- dataCollectionModule.appDataCollector.get(),
+ dataCollectionModule.appDataCollector,
dataCollectionModule.deviceDataCollector,
- trackerModule.sessionTracker.get(),
+ trackerModule.sessionTracker,
notifier,
bgTaskService
) else null
@@ -43,7 +43,7 @@ internal class EventStorageModule(
cfg.logger,
notifier,
bgTaskService,
- delegate.getOrNull(),
+ delegate,
callbackState
)
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt
index 5d68837b05..e11fe96ba6 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt
@@ -9,6 +9,7 @@ import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.ForegroundDetector
import com.bugsnag.android.internal.ImmutableConfig
import com.bugsnag.android.internal.TaskType
+import com.bugsnag.android.internal.dag.Provider
import java.io.File
import java.util.Calendar
import java.util.Date
@@ -27,7 +28,7 @@ internal class EventStore(
logger: Logger,
notifier: Notifier,
bgTaskService: BackgroundTaskService,
- delegate: Delegate?,
+ delegate: Provider?,
callbackState: CallbackState
) : FileStore(
File(config.persistenceDirectory.value, "bugsnag/errors"),
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt
index f7ede678cb..aa49df6d54 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.kt
@@ -1,6 +1,7 @@
package com.bugsnag.android
import com.bugsnag.android.JsonStream.Streamable
+import com.bugsnag.android.internal.dag.Provider
import java.io.BufferedWriter
import java.io.File
import java.io.FileNotFoundException
@@ -15,7 +16,7 @@ internal abstract class FileStore(
val storageDir: File,
private val maxStoreCount: Int,
protected open val logger: Logger,
- protected val delegate: Delegate?
+ protected val delegate: Provider?
) {
internal fun interface Delegate {
/**
@@ -66,7 +67,7 @@ internal abstract class FileStore(
out.write(content)
} catch (exc: Exception) {
val eventFile = File(filePath)
- delegate?.onErrorIOFailure(exc, eventFile, "NDK Crash report copy")
+ delegate?.getOrNull()?.onErrorIOFailure(exc, eventFile, "NDK Crash report copy")
IOUtils.deleteFile(eventFile, logger)
} finally {
try {
@@ -100,7 +101,7 @@ internal abstract class FileStore(
logger.w("Ignoring FileNotFoundException - unable to create file", exc)
} catch (exc: Exception) {
val eventFile = File(filename)
- delegate?.onErrorIOFailure(exc, eventFile, "Crash report serialization")
+ delegate?.getOrNull()?.onErrorIOFailure(exc, eventFile, "Crash report serialization")
IOUtils.deleteFile(eventFile, logger)
} finally {
IOUtils.closeQuietly(stream)
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java
index ad1d791bdf..f45c764a92 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java
@@ -32,10 +32,10 @@ class InternalReportDelegate implements EventStore.Delegate {
@Nullable
final StorageManager storageManager;
- final AppDataCollector appDataCollector;
+ final Provider appDataCollector;
final Provider deviceDataCollector;
final Context appContext;
- final SessionTracker sessionTracker;
+ final Provider sessionTracker;
final Notifier notifier;
final BackgroundTaskService backgroundTaskService;
@@ -43,9 +43,9 @@ class InternalReportDelegate implements EventStore.Delegate {
Logger logger,
ImmutableConfig immutableConfig,
@Nullable StorageManager storageManager,
- AppDataCollector appDataCollector,
+ Provider appDataCollector,
Provider deviceDataCollector,
- SessionTracker sessionTracker,
+ Provider sessionTracker,
Notifier notifier,
BackgroundTaskService backgroundTaskService) {
this.logger = logger;
@@ -101,7 +101,7 @@ void recordStorageCacheBehavior(Event event) {
* This is intended for internal use only, and reports will not be visible to end-users.
*/
void reportInternalBugsnagError(@NonNull Event event) {
- event.setApp(appDataCollector.generateAppWithState());
+ event.setApp(appDataCollector.get().generateAppWithState());
event.setDevice(deviceDataCollector.get().generateDeviceWithState(new Date().getTime()));
event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierName", notifier.getName());
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
index 69e15b30c3..a82c6a6439 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Notifier.kt
@@ -7,7 +7,7 @@ import java.io.IOException
*/
class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier",
- var version: String = "6.14.0",
+ var version: String = "6.15.0",
var url: String = "https://bugsnag.com"
) : JsonStream.Streamable {
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt
index c187121d5e..7be09c0b0a 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/RootDetector.kt
@@ -1,9 +1,14 @@
package com.bugsnag.android
+import android.os.Build
+import android.os.SystemClock
import androidx.annotation.VisibleForTesting
import java.io.File
import java.io.IOException
import java.io.Reader
+import java.lang.Thread
+import java.util.concurrent.TimeUnit
+import kotlin.math.min
/**
* Attempts to detect whether the device is rooted. Root detection errs on the side of false
@@ -21,6 +26,9 @@ internal class RootDetector @JvmOverloads constructor(
) {
companion object {
+ private const val PROCESS_TIMEOUT = 250L
+ private const val PROCESS_POLL_DELAY = 50L
+
private val BUILD_PROP_FILE = File("/system/build.prop")
private val ROOT_INDICATORS = listOf(
@@ -120,7 +128,20 @@ internal class RootDetector @JvmOverloads constructor(
var process: Process? = null
return try {
process = processBuilder.start()
+ val processComplete = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ process.waitFor(PROCESS_TIMEOUT, TimeUnit.MILLISECONDS)
+ } else {
+ process.fallbackWaitFor(PROCESS_TIMEOUT)
+ }
+
+ if (!processComplete) {
+ return false
+ }
+
process.inputStream.bufferedReader().use { it.isNotBlank() }
+ } catch (ignored: InterruptedException) {
+ Thread.currentThread().interrupt() // restore the interrupted status
+ false
} catch (ignored: IOException) {
false
} finally {
@@ -147,4 +168,19 @@ internal class RootDetector @JvmOverloads constructor(
libraryLoaded -> performNativeRootChecks()
else -> false
}
+
+ private fun Process.fallbackWaitFor(timeout: Long): Boolean {
+ val endTime = SystemClock.elapsedRealtime() + timeout
+ while (SystemClock.elapsedRealtime() < endTime) {
+ try {
+ exitValue()
+ return true
+ } catch (ex: IllegalThreadStateException) {
+ // Process is still running, wait a bit before checking again
+ Thread.sleep(min(PROCESS_POLL_DELAY, endTime - SystemClock.elapsedRealtime()))
+ }
+ }
+
+ return false
+ }
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt
index e6e0ce6aa8..82241ca949 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt
@@ -2,6 +2,7 @@ package com.bugsnag.android
import com.bugsnag.android.SessionFilenameInfo.Companion.defaultFilename
import com.bugsnag.android.SessionFilenameInfo.Companion.findTimestampInFilename
+import com.bugsnag.android.internal.dag.Provider
import java.io.File
import java.util.Calendar
import java.util.Comparator
@@ -16,7 +17,7 @@ internal class SessionStore(
maxPersistedSessions: Int,
private val apiKey: String,
logger: Logger,
- delegate: Delegate?
+ delegate: Provider?
) : FileStore(
File(bugsnagDir, "sessions"),
maxPersistedSessions,
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 25fb7058c4..82d1c80575 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
@@ -5,6 +5,7 @@
import com.bugsnag.android.internal.ForegroundDetector;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.TaskType;
+import com.bugsnag.android.internal.dag.Provider;
import android.app.Activity;
@@ -32,7 +33,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
private final ImmutableConfig configuration;
private final CallbackState callbackState;
private final Client client;
- final SessionStore sessionStore;
+ final Provider sessionStore;
private volatile Session currentSession = null;
final BackgroundTaskService backgroundTaskService;
final Logger logger;
@@ -41,7 +42,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
SessionTracker(ImmutableConfig configuration,
CallbackState callbackState,
Client client,
- SessionStore sessionStore,
+ Provider sessionStore,
Logger logger,
BackgroundTaskService backgroundTaskService) {
this(configuration, callbackState, client, DEFAULT_TIMEOUT_MS,
@@ -52,7 +53,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
CallbackState callbackState,
Client client,
long timeoutMs,
- SessionStore sessionStore,
+ Provider sessionStore,
Logger logger,
BackgroundTaskService backgroundTaskService) {
this.configuration = configuration;
@@ -263,7 +264,7 @@ public void run() {
* Attempts to flush session payloads stored on disk
*/
void flushStoredSessions() {
- List storedFiles = sessionStore.findStoredFiles();
+ List storedFiles = sessionStore.get().findStoredFiles();
for (File storedFile : storedFiles) {
flushStoredSession(storedFile);
@@ -282,27 +283,28 @@ void flushStoredSession(File storedFile) {
}
DeliveryStatus deliveryStatus = deliverSessionPayload(payload);
+ SessionStore store = sessionStore.get();
switch (deliveryStatus) {
case DELIVERED:
- sessionStore.deleteStoredFiles(Collections.singletonList(storedFile));
+ store.deleteStoredFiles(Collections.singletonList(storedFile));
logger.d("Sent 1 new session to Bugsnag");
break;
case UNDELIVERED:
- if (sessionStore.isTooOld(storedFile)) {
+ if (store.isTooOld(storedFile)) {
logger.w("Discarding historical session (from {"
- + sessionStore.getCreationDate(storedFile)
+ + store.getCreationDate(storedFile)
+ "}) after failed delivery");
- sessionStore.deleteStoredFiles(Collections.singletonList(storedFile));
+ store.deleteStoredFiles(Collections.singletonList(storedFile));
} else {
- sessionStore.cancelQueuedFiles(Collections.singletonList(storedFile));
+ store.cancelQueuedFiles(Collections.singletonList(storedFile));
logger.w("Leaving session payload for future delivery");
}
break;
case FAILURE:
// drop bad data
logger.w("Deleting invalid session tracking payload");
- sessionStore.deleteStoredFiles(Collections.singletonList(storedFile));
+ store.deleteStoredFiles(Collections.singletonList(storedFile));
break;
default:
break;
@@ -319,7 +321,7 @@ public void run() {
});
} catch (RejectedExecutionException exception) {
// This is on the current thread but there isn't much else we can do
- sessionStore.write(session);
+ sessionStore.get().write(session);
}
}
@@ -331,7 +333,7 @@ void deliverInMemorySession(Session session) {
switch (deliveryStatus) {
case UNDELIVERED:
logger.w("Storing session payload for future delivery");
- sessionStore.write(session);
+ sessionStore.get().write(session);
break;
case FAILURE:
logger.w("Dropping invalid session tracking payload");
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt
index 8eb6c47607..afc2be9779 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt
@@ -26,7 +26,7 @@ internal class TrackerModule(
config,
callbackState,
client,
- storageModule.sessionStore.get(),
+ storageModule.sessionStore,
config.logger,
bgTaskService
)
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt
index 93f33991a7..4f58d2b6fe 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorSerializationTest.kt
@@ -5,6 +5,7 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.bugsnag.android.BugsnagTestUtils.convert
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -49,7 +50,7 @@ internal class AppDataCollectorSerializationTest {
context,
pm,
convert(config),
- sessionTracker,
+ ValueProvider(sessionTracker),
am,
launchCrashTracker,
memoryTrimState
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt
index 70cf84c87b..829468dcd8 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppMetadataSerializationTest.kt
@@ -5,6 +5,7 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.bugsnag.android.internal.convertToImmutableConfig
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertNotNull
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +51,7 @@ internal class AppMetadataSerializationTest {
context,
pm,
convertToImmutableConfig(config, null, null, ApplicationInfo()),
- sessionTracker,
+ ValueProvider(sessionTracker),
am,
launchCrashTracker,
memoryTrimState
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt
index 6c802250a3..13da2d8cd4 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EmptyEventCallbackTest.kt
@@ -6,6 +6,7 @@ import com.bugsnag.android.FileStore.Delegate
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.ImmutableConfig
import com.bugsnag.android.internal.convertToImmutableConfig
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -142,14 +143,16 @@ class EmptyEventCallbackTest {
NoopLogger,
Notifier(),
backgroundTaskService,
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt
index 53742e044a..d042eeafb0 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventFilenameTest.kt
@@ -3,6 +3,7 @@ package com.bugsnag.android
import com.bugsnag.android.EventStore.Companion.EVENT_COMPARATOR
import com.bugsnag.android.FileStore.Delegate
import com.bugsnag.android.internal.BackgroundTaskService
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -58,14 +59,16 @@ internal class EventFilenameTest {
NoopLogger,
Notifier(),
BackgroundTaskService(),
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
@@ -87,14 +90,16 @@ internal class EventFilenameTest {
NoopLogger,
Notifier(),
BackgroundTaskService(),
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
@@ -110,14 +115,16 @@ internal class EventFilenameTest {
NoopLogger,
Notifier(),
BackgroundTaskService(),
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt
index 4bcf5f964f..5e21302fc2 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/EventStoreMaxLimitTest.kt
@@ -6,6 +6,7 @@ import com.bugsnag.android.FileStore.Delegate
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.ImmutableConfig
import com.bugsnag.android.internal.convertToImmutableConfig
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -84,14 +85,16 @@ class EventStoreMaxLimitTest {
NoopLogger,
Notifier(),
BackgroundTaskService(),
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt
index d17dac578f..666e412131 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt
@@ -53,9 +53,9 @@ internal class InternalEventPayloadDelegateTest {
NoopLogger,
config,
storageManager,
- appDataCollector,
+ ValueProvider(appDataCollector),
ValueProvider(deviceDataCollector),
- sessionTracker,
+ ValueProvider(sessionTracker),
Notifier(),
BackgroundTaskService()
)
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt
index 401e8fd1fa..1a6fe613a8 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/LaunchCrashDeliveryTest.kt
@@ -4,6 +4,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateConfiguration
import com.bugsnag.android.BugsnagTestUtils.generateEvent
import com.bugsnag.android.FileStore.Delegate
import com.bugsnag.android.internal.BackgroundTaskService
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -161,14 +162,16 @@ class LaunchCrashDeliveryTest {
NoopLogger,
Notifier(),
backgroundTaskService,
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- },
+ ),
CallbackState()
)
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt
index 1a3e4c6f24..b9a17648b3 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt
@@ -5,6 +5,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateSession
import com.bugsnag.android.FileStore.Delegate
import com.bugsnag.android.internal.ImmutableConfig
import com.bugsnag.android.internal.convertToImmutableConfig
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -83,14 +84,16 @@ class SessionStoreMaxLimitTest {
config.maxPersistedSessions,
config.apiKey,
NoopLogger,
- object : Delegate {
- override fun onErrorIOFailure(
- exception: Exception?,
- errorFile: File?,
- context: String?
- ) {
+ ValueProvider(
+ object : Delegate {
+ override fun onErrorIOFailure(
+ exception: Exception?,
+ errorFile: File?,
+ context: String?
+ ) {
+ }
}
- }
+ )
)
}
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt
index b2339cf096..1d82e5e354 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerPauseResumeTest.kt
@@ -5,6 +5,7 @@ import com.bugsnag.android.BugsnagTestUtils.generateConfiguration
import com.bugsnag.android.BugsnagTestUtils.generateDevice
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.ImmutableConfig
+import com.bugsnag.android.internal.dag.ValueProvider
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
@@ -60,7 +61,7 @@ internal class SessionTrackerPauseResumeTest {
BugsnagTestUtils.generateImmutableConfig(),
configuration.impl.callbackState,
client,
- sessionStore,
+ ValueProvider(sessionStore),
NoopLogger,
BackgroundTaskService()
)
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java
index 172ee3a22b..7dfe12a158 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionTrackerTest.java
@@ -10,6 +10,7 @@
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ForegroundDetector;
import com.bugsnag.android.internal.ImmutableConfig;
+import com.bugsnag.android.internal.dag.ValueProvider;
import androidx.annotation.NonNull;
@@ -71,8 +72,8 @@ public void setUp() {
configuration.setDelivery(BugsnagTestUtils.generateDelivery());
immutableConfig = BugsnagTestUtils.generateImmutableConfig();
sessionTracker = new SessionTracker(immutableConfig,
- configuration.impl.callbackState, client, sessionStore, NoopLogger.INSTANCE,
- bgTaskService);
+ configuration.impl.callbackState, client, new ValueProvider<>(sessionStore),
+ NoopLogger.INSTANCE, bgTaskService);
configuration.setAutoTrackSessions(true);
user = new User(null, null, null);
@@ -179,7 +180,7 @@ public void testBasicInForeground() {
public void testZeroSessionTimeout() {
CallbackState callbackState = configuration.impl.callbackState;
sessionTracker = new SessionTracker(immutableConfig, callbackState, client,
- 0, sessionStore, NoopLogger.INSTANCE, bgTaskService);
+ 0, new ValueProvider<>(sessionStore), NoopLogger.INSTANCE, bgTaskService);
long now = System.currentTimeMillis();
sessionTracker.onForegroundStatus(true, now);
@@ -199,7 +200,7 @@ public void testZeroSessionTimeout() {
public void testSessionTimeout() {
CallbackState callbackState = configuration.impl.callbackState;
sessionTracker = new SessionTracker(immutableConfig, callbackState, client,
- 100, sessionStore, NoopLogger.INSTANCE, bgTaskService);
+ 100, new ValueProvider<>(sessionStore), NoopLogger.INSTANCE, bgTaskService);
long now = System.currentTimeMillis();
sessionTracker.onForegroundStatus(true, now);
diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml
index 3bd6751cd5..ea05089591 100644
--- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml
+++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml
@@ -27,7 +27,8 @@
MaxLineLength:TraceParserTest.kt$TraceParserTest$"void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (android::AsyncWorker::*)(), android::AsyncWorker*> >(void*)"
NestedBlockDepth:TraceParser.kt$TraceParser$private fun parseThreadAttributes(line: String)
ReturnCount:TraceParser.kt$TraceParser$@VisibleForTesting internal fun parseNativeFrame(line: String): Stackframe?
- SwallowedException:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin$e: Exception
+ SwallowedException:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin.SessionProcessStateSummaryCallback$e: Exception
+ SwallowedException:BugsnagExitInfoPlugin.kt$e: Exception
SwallowedException:ExitInfoCallback.kt$ExitInfoCallback$exc: Throwable
SwallowedException:ExitInfoPluginStore.kt$ExitInfoPluginStore$exc: Throwable
diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt
index cbf11895fa..be6368031a 100644
--- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt
+++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt
@@ -10,6 +10,7 @@ import android.os.Process
import androidx.annotation.RequiresApi
import com.bugsnag.android.ApplicationExitInfoMatcher.Companion.MATCH_ALL
import com.bugsnag.android.ApplicationExitInfoMatcher.Companion.MAX_EXIT_INFO
+import com.bugsnag.android.internal.TaskType
@RequiresApi(Build.VERSION_CODES.R)
class BugsnagExitInfoPlugin @JvmOverloads constructor(
@@ -21,13 +22,7 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor(
@SuppressLint("VisibleForTests")
override fun load(client: Client) {
if (!configuration.disableProcessStateSummaryOverride) {
- client.addOnSession(
- OnSessionCallback { session: Session ->
- val am = client.appContext.safeGetActivityManager()
- am?.setProcessStateSummary(session.id.toByteArray())
- return@OnSessionCallback true
- }
- )
+ client.addOnSession(SessionProcessStateSummaryCallback(client))
}
val tombstoneEventEnhancer = TombstoneEventEnhancer(
@@ -146,14 +141,29 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor(
}
override fun unload() = Unit
-
- private fun Context.safeGetActivityManager(): ActivityManager? = try {
- getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
- } catch (e: Exception) {
- null
- }
-
private fun Context.isPrimaryProcess(): Boolean {
return Application.getProcessName() == packageName
}
+
+ private class SessionProcessStateSummaryCallback(
+ private val client: Client
+ ) : OnSessionCallback {
+ override fun onSession(session: Session): Boolean {
+ val am = client.appContext.safeGetActivityManager() ?: return true
+ client.bgTaskService.submitTask(TaskType.DEFAULT) {
+ try {
+ am.setProcessStateSummary(session.id.toByteArray())
+ } catch (e: Exception) {
+ // this can be rate limited by the system, so we ignore it
+ }
+ }
+ return true
+ }
+ }
+}
+
+internal fun Context.safeGetActivityManager(): ActivityManager? = try {
+ getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+} catch (e: Exception) {
+ null
}
diff --git a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api
index 4b7eea278f..1136fc1740 100644
--- a/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api
+++ b/bugsnag-plugin-android-ndk/api/bugsnag-plugin-android-ndk.api
@@ -33,7 +33,6 @@ public final class com/bugsnag/android/ndk/NativeBridge : com/bugsnag/android/in
public final fun updateContext (Ljava/lang/String;)V
public final fun updateInForeground (ZLjava/lang/String;)V
public final fun updateIsLaunching (Z)V
- public final fun updateLastRunInfo (I)V
public final fun updateLowMemory (ZLjava/lang/String;)V
public final fun updateOrientation (Ljava/lang/String;)V
public final fun updateUserEmail (Ljava/lang/String;)V
diff --git a/bugsnag-plugin-android-ndk/build.gradle.kts b/bugsnag-plugin-android-ndk/build.gradle.kts
index 51a08ab826..089b80df0f 100644
--- a/bugsnag-plugin-android-ndk/build.gradle.kts
+++ b/bugsnag-plugin-android-ndk/build.gradle.kts
@@ -1,5 +1,6 @@
plugins {
loadDefaultPlugins()
+ id("bugsnag-jni-link-table-plugin")
}
android {
diff --git a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt
index 7bcebcccf6..acb5079306 100644
--- a/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt
+++ b/bugsnag-plugin-android-ndk/src/main/CMakeLists.txt
@@ -35,8 +35,6 @@ include_directories(
jni/external/libunwindstack-ndk/include
)
-target_include_directories(bugsnag-ndk PRIVATE ${BUGSNAG_DIR}/assets/include)
-
target_link_libraries(
# Specifies the target library.
bugsnag-ndk
diff --git a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt
index 94204bd8fa..fda9392331 100644
--- a/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt
+++ b/bugsnag-plugin-android-ndk/src/main/exported_native_symbols-RelWithDebInfo.txt
@@ -6,6 +6,7 @@ global:
__emutls_get_address;
__gxx_personality_v0;
Java_*;
+ JNI_OnLoad;
extern "C++" {
"std::get_terminate()";
"std::set_terminate(void (*)())";
diff --git a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt
index 793eec21ef..dae23bd689 100644
--- a/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt
+++ b/bugsnag-plugin-android-ndk/src/main/java/com/bugsnag/android/ndk/NativeBridge.kt
@@ -84,13 +84,12 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse
external fun updateContext(context: String)
external fun updateInForeground(inForeground: Boolean, activityName: String)
external fun updateIsLaunching(isLaunching: Boolean)
- external fun updateLastRunInfo(consecutiveLaunchCrashes: Int)
external fun updateOrientation(orientation: String)
external fun updateUserId(newValue: String)
external fun updateUserEmail(newValue: String)
external fun updateUserName(newValue: String)
- external fun getSignalUnwindStackFunction(): Long
external fun updateLowMemory(newValue: Boolean, memoryTrimLevelDescription: String)
+ external fun getSignalUnwindStackFunction(): Long
external fun addFeatureFlag(name: String, variant: String?)
external fun clearFeatureFlag(name: String)
external fun clearFeatureFlags()
@@ -139,7 +138,6 @@ class NativeBridge(private val bgTaskService: BackgroundTaskService) : StateObse
event.contextActivity ?: ""
)
- is StateEvent.UpdateLastRunInfo -> updateLastRunInfo(event.consecutiveLaunchCrashes)
is StateEvent.UpdateIsLaunching -> {
updateIsLaunching(event.isLaunching)
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
index a0dbdeed20..14f735d0c3 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
+++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c
@@ -84,8 +84,7 @@ void bsg_finish_handling_crash() {
bsg_global_env->handling_crash = false;
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_enableCrashReporting(
- JNIEnv *env, jobject _this) {
+static void JNI_NdkPlugin_enableCrashReporting(JNIEnv *env, jobject _this) {
if (bsg_global_env == NULL) {
BUGSNAG_LOG(
"Attempted to enable crash reporting without first calling install()");
@@ -95,14 +94,12 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_enableCrashReporting(
bsg_handler_install_cpp(bsg_global_env);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_NdkPlugin_disableCrashReporting(
- JNIEnv *env, jobject _this) {
+static void JNI_NdkPlugin_disableCrashReporting(JNIEnv *env, jobject _this) {
bsg_handler_uninstall_signal();
bsg_handler_uninstall_cpp();
}
-JNIEXPORT jstring JNICALL
-Java_com_bugsnag_android_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) {
+static jstring JNI_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) {
#if defined(__i386__)
const char *binary_arch = "x86";
#elif defined(__x86_64__)
@@ -117,9 +114,7 @@ Java_com_bugsnag_android_NdkPlugin_getBinaryArch(JNIEnv *env, jobject _this) {
return bsg_safe_new_string_utf(env, binary_arch);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_enableCrashReporting(JNIEnv *env,
- jobject _this) {
+static void JNI_NativeBridge_enableCrashReporting(JNIEnv *env, jobject _this) {
if (bsg_global_env == NULL) {
BUGSNAG_LOG(
"Attempted to enable crash reporting without first calling install()");
@@ -129,9 +124,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_enableCrashReporting(JNIEnv *env,
bsg_handler_install_cpp(bsg_global_env);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_disableCrashReporting(JNIEnv *env,
- jobject _this) {
+static void JNI_NativeBridge_disableCrashReporting(JNIEnv *env, jobject _this) {
bsg_handler_uninstall_signal();
bsg_handler_uninstall_cpp();
}
@@ -152,12 +145,14 @@ void bsg_update_next_run_info(bsg_environment *env) {
launch_crashes, crashed_value);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install(
- JNIEnv *env, jobject _this, jstring _api_key, jstring _event_path,
- jstring _last_run_info_path, jstring _event_uuid,
- jint consecutive_launch_crashes, jboolean auto_detect_ndk_crashes,
- jint _api_level, jboolean is32bit, jint send_threads,
- jint max_breadcrumbs) {
+static void JNI_NativeBridge_install(JNIEnv *env, jobject _this,
+ jstring _api_key, jstring _event_path,
+ jstring _last_run_info_path,
+ jstring _event_uuid,
+ jint consecutive_launch_crashes,
+ jboolean auto_detect_ndk_crashes,
+ jint _api_level, jboolean is32bit,
+ jint send_threads, jint max_breadcrumbs) {
if (!bsg_jni_cache_init(env)) {
BUGSNAG_LOG("Could not init JNI jni_cache.");
@@ -255,9 +250,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_install(
free(bugsnag_env);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env,
- jobject _this) {
+static void JNI_NativeBridge_addHandledEvent(JNIEnv *env, jobject _this) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -270,9 +263,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_addHandledEvent(JNIEnv *env,
release_env_write_lock();
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env,
- jobject _this) {
+static void JNI_NativeBridge_addUnhandledEvent(JNIEnv *env, jobject _this) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -285,9 +276,11 @@ Java_com_bugsnag_android_ndk_NativeBridge_addUnhandledEvent(JNIEnv *env,
release_env_write_lock();
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession(
- JNIEnv *env, jobject _this, jstring session_id_, jstring start_date_,
- jint handled_count, jint unhandled_count) {
+static void JNI_NativeBridge_startedSession(JNIEnv *env, jobject _this,
+ jstring session_id_,
+ jstring start_date_,
+ jint handled_count,
+ jint unhandled_count) {
if (session_id_ == NULL) {
return;
}
@@ -306,8 +299,7 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_startedSession(
bsg_safe_release_string_utf_chars(env, start_date_, started_at);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession(
- JNIEnv *env, jobject _this) {
+static void JNI_NativeBridge_pausedSession(JNIEnv *env, jobject _this) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -320,9 +312,10 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_pausedSession(
release_env_write_lock();
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb(
- JNIEnv *env, jobject _this, jstring name_, jint crumb_type,
- jstring timestamp_, jobject metadata) {
+static void JNI_NativeBridge_addBreadcrumb(JNIEnv *env, jobject _this,
+ jstring name_, jint crumb_type,
+ jstring timestamp_,
+ jobject metadata) {
if (!bsg_jni_cache->initialized) {
BUGSNAG_LOG("addBreadcrumb failed: JNI cache not initialized.");
@@ -386,10 +379,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addBreadcrumb(
bsg_safe_release_string_utf_chars(env, timestamp_, timestamp);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateAppVersion(JNIEnv *env,
- jobject _this,
- jstring new_value) {
+static void JNI_NativeBridge_updateAppVersion(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -404,10 +395,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateAppVersion(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, new_value, value);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateBuildUUID(JNIEnv *env,
- jobject _this,
- jstring new_value) {
+static void JNI_NativeBridge_updateBuildUUID(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -421,8 +410,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateBuildUUID(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, new_value, value);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext(
- JNIEnv *env, jobject _this, jstring new_value) {
+static void JNI_NativeBridge_updateContext(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -439,9 +428,9 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateContext(
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground(
- JNIEnv *env, jobject _this, jboolean new_value, jstring activity_) {
+static void JNI_NativeBridge_updateInForeground(JNIEnv *env, jobject _this,
+ jboolean new_value,
+ jstring activity_) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -465,9 +454,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateInForeground(
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching(
- JNIEnv *env, jobject _this, jboolean new_value) {
+static void JNI_NativeBridge_updateIsLaunching(JNIEnv *env, jobject _this,
+ jboolean new_value) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -477,10 +465,10 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching(
release_env_write_lock();
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory(
- JNIEnv *env, jobject _this, jboolean low_memory,
- jstring memory_trim_level_description) {
+static void
+JNI_NativeBridge_updateLowMemory(JNIEnv *env, jobject _this,
+ jboolean low_memory,
+ jstring memory_trim_level_description) {
char *memory_trim_level =
(char *)bsg_safe_get_string_utf_chars(env, memory_trim_level_description);
@@ -505,10 +493,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory(
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env,
- jobject _this,
- jstring new_value) {
+static void JNI_NativeBridge_updateOrientation(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -526,9 +512,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateOrientation(JNIEnv *env,
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage(
- JNIEnv *env, jobject _this, jstring new_value) {
+static void JNI_NativeBridge_updateReleaseStage(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -545,8 +530,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateReleaseStage(
}
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId(
- JNIEnv *env, jobject _this, jstring new_value) {
+static void JNI_NativeBridge_updateUserId(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -566,8 +551,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserId(
}
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserName(
- JNIEnv *env, jobject _this, jstring new_value) {
+static void JNI_NativeBridge_updateUserName(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -587,10 +572,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateUserName(
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env,
- jobject _this,
- jstring new_value) {
+static void JNI_NativeBridge_updateUserEmail(JNIEnv *env, jobject _this,
+ jstring new_value) {
char *value = (char *)bsg_safe_get_string_utf_chars(env, new_value);
if (value == NULL) {
return;
@@ -610,9 +593,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateUserEmail(JNIEnv *env,
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString(
- JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) {
+static void JNI_NativeBridge_addMetadataString(JNIEnv *env, jobject _this,
+ jstring tab_, jstring key_,
+ jstring value_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
char *value = (char *)bsg_safe_get_string_utf_chars(env, value_);
@@ -631,9 +614,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataString(
bsg_safe_release_string_utf_chars(env, value_, value);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble(
- JNIEnv *env, jobject _this, jstring tab_, jstring key_, jdouble value_) {
+static void JNI_NativeBridge_addMetadataDouble(JNIEnv *env, jobject _this,
+ jstring tab_, jstring key_,
+ jdouble value_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
if (tab != NULL && key != NULL) {
@@ -650,9 +633,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataDouble(
bsg_safe_release_string_utf_chars(env, key_, key);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean(
- JNIEnv *env, jobject _this, jstring tab_, jstring key_, jboolean value_) {
+static void JNI_NativeBridge_addMetadataBoolean(JNIEnv *env, jobject _this,
+ jstring tab_, jstring key_,
+ jboolean value_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
if (tab != NULL && key != NULL) {
@@ -669,9 +652,9 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataBoolean(
bsg_safe_release_string_utf_chars(env, key_, key);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_addMetadataOpaque(
- JNIEnv *env, jobject _this, jstring tab_, jstring key_, jstring value_) {
+static void JNI_NativeBridge_addMetadataOpaque(JNIEnv *env, jobject _this,
+ jstring tab_, jstring key_,
+ jstring value_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
char *value = (char *)bsg_safe_get_string_utf_chars(env, value_);
@@ -690,10 +673,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_addMetadataOpaque(
bsg_safe_release_string_utf_chars(env, value_, value);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_clearMetadataTab(JNIEnv *env,
- jobject _this,
- jstring tab_) {
+static void JNI_NativeBridge_clearMetadataTab(JNIEnv *env, jobject _this,
+ jstring tab_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
if (tab == NULL) {
return;
@@ -708,8 +689,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearMetadataTab(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, tab_, tab);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_removeMetadata(
- JNIEnv *env, jobject _this, jstring tab_, jstring key_) {
+static void JNI_NativeBridge_removeMetadata(JNIEnv *env, jobject _this,
+ jstring tab_, jstring key_) {
char *tab = (char *)bsg_safe_get_string_utf_chars(env, tab_);
char *key = (char *)bsg_safe_get_string_utf_chars(env, key_);
@@ -727,8 +708,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_removeMetadata(
bsg_safe_release_string_utf_chars(env, key_, key);
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata(
- JNIEnv *env, jobject _this, jobject metadata) {
+static void JNI_NativeBridge_updateMetadata(JNIEnv *env, jobject _this,
+ jobject metadata) {
if (!bsg_jni_cache->initialized) {
BUGSNAG_LOG("updateMetadata failed: JNI cache not initialized.");
return;
@@ -742,14 +723,13 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_updateMetadata(
release_env_write_lock();
}
-JNIEXPORT jlong JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_getSignalUnwindStackFunction(
- JNIEnv *env, jobject thiz) {
+static jlong JNI_NativeBridge_getSignalUnwindStackFunction(JNIEnv *env,
+ jobject thiz) {
return (jlong)bsg_unwind_crash_stack;
}
-JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag(
- JNIEnv *env, jobject thiz, jstring name_, jstring variant_) {
+static void JNI_NativeBridge_addFeatureFlag(JNIEnv *env, jobject thiz,
+ jstring name_, jstring variant_) {
char *name = (char *)bsg_safe_get_string_utf_chars(env, name_);
char *variant = (char *)bsg_safe_get_string_utf_chars(env, variant_);
@@ -768,10 +748,8 @@ JNIEXPORT void JNICALL Java_com_bugsnag_android_ndk_NativeBridge_addFeatureFlag(
bsg_safe_release_string_utf_chars(env, variant_, variant);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlag(JNIEnv *env,
- jobject thiz,
- jstring name_) {
+static void JNI_NativeBridge_clearFeatureFlag(JNIEnv *env, jobject thiz,
+ jstring name_) {
char *name = (char *)bsg_safe_get_string_utf_chars(env, name_);
@@ -788,9 +766,7 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlag(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, name_, name);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlags(JNIEnv *env,
- jobject thiz) {
+static void JNI_NativeBridge_clearFeatureFlags(JNIEnv *env, jobject thiz) {
bsg_environment *bsg_env = request_env_write_lock();
if (bsg_env == NULL) {
return;
@@ -799,15 +775,12 @@ Java_com_bugsnag_android_ndk_NativeBridge_clearFeatureFlags(JNIEnv *env,
release_env_write_lock();
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_refreshSymbolTable(JNIEnv *env,
- jobject thiz) {
+static void JNI_NativeBridge_refreshSymbolTable(JNIEnv *env, jobject thiz) {
bugsnag_refresh_symbol_table();
}
-JNIEXPORT jobject JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts(
- JNIEnv *env, jobject thiz) {
+static jobject JNI_NativeBridge_getCurrentCallbackSetCounts(JNIEnv *env,
+ jobject thiz) {
if (bsg_global_env == NULL || bsg_jni_cache == NULL) {
return NULL;
@@ -839,9 +812,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_getCurrentCallbackSetCounts(
return counts;
}
-JNIEXPORT jobject JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_getCurrentNativeApiCallUsage(
- JNIEnv *env, jobject thiz) {
+static jobject JNI_NativeBridge_getCurrentNativeApiCallUsage(JNIEnv *env,
+ jobject thiz) {
if (bsg_global_env == NULL || bsg_jni_cache == NULL) {
return NULL;
}
@@ -869,10 +841,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_getCurrentNativeApiCallUsage(
return map;
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_setStaticJsonData(JNIEnv *env,
- jobject thiz,
- jstring data_) {
+static void JNI_NativeBridge_setStaticJsonData(JNIEnv *env, jobject thiz,
+ jstring data_) {
if (bsg_global_env == NULL) {
return;
}
@@ -900,10 +870,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_setStaticJsonData(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, data_, data);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_initCallbackCounts(JNIEnv *env,
- jobject thiz,
- jobject counts) {
+static void JNI_NativeBridge_initCallbackCounts(JNIEnv *env, jobject thiz,
+ jobject counts) {
jobject entrySet =
bsg_safe_call_object_method(env, counts, bsg_jni_cache->Map_entrySet);
jobject iterator =
@@ -925,10 +893,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_initCallbackCounts(JNIEnv *env,
}
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_notifyAddCallback(JNIEnv *env,
- jobject thiz,
- jstring callback_) {
+static void JNI_NativeBridge_notifyAddCallback(JNIEnv *env, jobject thiz,
+ jstring callback_) {
const char *callback = bsg_safe_get_string_utf_chars(env, callback_);
if (!callback) {
return;
@@ -944,9 +910,8 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyAddCallback(JNIEnv *env,
bsg_safe_release_string_utf_chars(env, callback_, callback);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_notifyRemoveCallback(
- JNIEnv *env, jobject thiz, jstring callback_) {
+static void JNI_NativeBridge_notifyRemoveCallback(JNIEnv *env, jobject thiz,
+ jstring callback_) {
const char *callback = bsg_safe_get_string_utf_chars(env, callback_);
if (!callback) {
return;
@@ -962,12 +927,58 @@ Java_com_bugsnag_android_ndk_NativeBridge_notifyRemoveCallback(
bsg_safe_release_string_utf_chars(env, callback_, callback);
}
-JNIEXPORT void JNICALL
-Java_com_bugsnag_android_ndk_NativeBridge_setInternalMetricsEnabled(
- JNIEnv *env, jobject thiz, jboolean enabled) {
+static void JNI_NativeBridge_setInternalMetricsEnabled(JNIEnv *env,
+ jobject thiz,
+ jboolean enabled) {
bsg_set_internal_metrics_enabled(enabled);
}
+// These headers are included here to ensure that the JNI methods are
+// declared (above) as static, avoiding the need to forward declare them
+
+#include "NativeBridge_JNI.h"
+#include "NdkPlugin_JNI.h"
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ // export all of the JNI functions and link them to the Java methods
+ JNIEnv *env = NULL;
+ jint result = -1;
+
+ if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
+ goto error;
+ }
+
+ if (env == NULL) {
+ goto error;
+ }
+
+ if (!bsg_jni_cache_init(env)) {
+ BUGSNAG_LOG("Could not init JNI jni_cache.");
+ goto error;
+ }
+
+ result = (*env)->RegisterNatives(env, bsg_jni_cache->NativeBridge,
+ NativeBridge_JNIMethods,
+ NativeBridge_JNI_METHODS_COUNT);
+ if (result < 0) {
+ BUGSNAG_LOG("Failed to register Bugsnag NativeBridge JNI methods");
+ goto error;
+ }
+
+ result = (*env)->RegisterNatives(env, bsg_jni_cache->NdkPlugin,
+ NdkPlugin_JNIMethods,
+ NdkPlugin_JNI_METHODS_COUNT);
+ if (result < 0) {
+ BUGSNAG_LOG("Failed to register Bugsnag NdkPlugin JNI methods");
+ goto error;
+ }
+
+ return JNI_VERSION_1_4;
+
+error:
+ return result;
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c
index 15f86c7fcb..300a9c73ca 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c
+++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.c
@@ -208,6 +208,9 @@ bool bsg_jni_cache_init(JNIEnv *env) {
CACHE_ENUM_CONSTANT(ErrorType_C, "com/bugsnag/android/ErrorType", "C");
+ CACHE_CLASS(NativeBridge, "com/bugsnag/android/ndk/NativeBridge");
+ CACHE_CLASS(NdkPlugin, "com/bugsnag/android/NdkPlugin");
+
pthread_key_create(&jni_cleanup_key, detach_java_env);
bsg_jni_cache->initialized = true;
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h
index 574b424d08..3528633637 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h
+++ b/bugsnag-plugin-android-ndk/src/main/jni/jni_cache.h
@@ -80,6 +80,9 @@ typedef struct {
jmethodID OpaqueValue_makeSafe;
jobject ErrorType_C;
+
+ jclass NativeBridge;
+ jclass NdkPlugin;
} bsg_jni_cache_t;
extern bsg_jni_cache_t *const bsg_jni_cache;
diff --git a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp
index 411b699e33..3fe7e90e52 100644
--- a/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp
+++ b/bugsnag-plugin-android-ndk/src/main/jni/utils/stack_unwinder.cpp
@@ -102,26 +102,28 @@ static void populate_code_identifier(const unwindstack::FrameData &frame,
}
auto shared_build_id = elf_fields->build_id_.load();
- std::string_view build_id;
if (shared_build_id == nullptr) {
auto elf = elf_fields->elf_.get();
if (elf == nullptr) {
return;
}
- build_id = elf->GetBuildID();
- } else {
- build_id = *shared_build_id;
- }
+ auto build_id = elf->GetBuildID();
+ if (build_id.empty()) {
+ return;
+ }
- if (build_id.empty()) {
- return;
+ // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own
+ // safe hex encoder to copy BuildID into code_identifier.
+ bsg_hex_encode(dst_frame.code_identifier, build_id.data(),
+ build_id.length(), sizeof(dst_frame.code_identifier));
+ } else if (!shared_build_id->empty()) {
+ std::string build_id = static_cast(*shared_build_id);
+ // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own
+ // safe hex encoder to copy BuildID into code_identifier.
+ bsg_hex_encode(dst_frame.code_identifier, build_id.data(),
+ build_id.length(), sizeof(dst_frame.code_identifier));
}
-
- // MapInfo.GetPrintableBuildID is *not* async-safe so we need our own
- // safe hex encoder to copy BuildID into code_identifier.
- bsg_hex_encode(dst_frame.code_identifier, build_id.data(), build_id.length(),
- sizeof(dst_frame.code_identifier));
}
void bsg_unwinder_refresh(void) {
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index bac8645f5d..9f7408cab1 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -4,8 +4,19 @@ plugins {
repositories {
mavenCentral()
+ google()
+}
+
+gradlePlugin {
+ plugins {
+ register("bugsnag-jni-link-table-plugin") {
+ id = "bugsnag-jni-link-table-plugin"
+ implementationClass = "com.bugsnag.android.gradle.JNILinkTablePlugin"
+ }
+ }
}
dependencies {
compileOnly(gradleApi())
+ implementation("com.android.tools.build:gradle-api:7.0.4")
}
diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt
new file mode 100644
index 0000000000..aba304f9d4
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/GenerateJNILinkTableTransform.kt
@@ -0,0 +1,135 @@
+package com.bugsnag.android.gradle
+
+import com.android.build.api.instrumentation.AsmClassVisitorFactory
+import com.android.build.api.instrumentation.ClassContext
+import com.android.build.api.instrumentation.ClassData
+import com.android.build.api.instrumentation.InstrumentationParameters
+import org.gradle.api.file.Directory
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+
+class GenerateJNILinkTableTransform(
+ api: Int,
+ classVisitor: ClassVisitor?,
+ val outputDirectory: Directory
+) : ClassVisitor(api, classVisitor) {
+
+ private var className: String? = null
+
+ private var nativeMethods: MutableList = mutableListOf()
+
+ override fun visit(
+ version: Int,
+ access: Int,
+ name: String?,
+ signature: String?,
+ superName: String?,
+ interfaces: Array?
+ ) {
+ className = name?.substringAfterLast('/')
+ super.visit(version, access, name, signature, superName, interfaces)
+ }
+
+ override fun visitEnd() {
+ // write the JNI link table for the class to header file
+ className?.let { className ->
+ if (nativeMethods.isNotEmpty()) {
+ generateLinkHeader(className, nativeMethods)
+ }
+ }
+
+ super.visitEnd()
+ }
+
+ private fun generateLinkHeader(className: String, methods: List) {
+ val headerFile = outputDirectory.file("${className}_JNI.h").asFile
+ headerFile.printWriter().use { writer ->
+ writer.println("// JNI Link Table for $className")
+ writer.println("#include ")
+ writer.println("#ifndef ${className}_JNI_H")
+ writer.println("#define ${className}_JNI_H")
+ writer.println()
+
+ writer.println("#define ${className}_JNI_METHODS_COUNT ${methods.size}")
+
+ writer.println("static const JNINativeMethod ${className}_JNIMethods[] = {");
+ for (i in methods.indices) {
+ val method = methods[i]
+
+ writer.print(" {\"")
+ writer.print(method.name)
+ writer.print("\", \"")
+ writer.print(method.descriptor)
+ writer.print("\", &JNI_")
+ writer.print(className)
+ writer.print('_')
+ writer.print(method.name)
+ writer.print("}")
+
+ if (i < methods.size - 1) {
+ writer.println(",")
+ } else {
+ writer.println()
+ }
+ }
+ writer.println("};");
+
+ writer.println()
+ writer.println("#endif // ${className}_JNI_H")
+ }
+ }
+
+ override fun visitMethod(
+ access: Int,
+ name: String?,
+ descriptor: String?,
+ signature: String?,
+ exceptions: Array?
+ ): MethodVisitor {
+ if (access and Opcodes.ACC_NATIVE != 0 && name != null && descriptor != null) {
+ nativeMethods.add(JNIMethod(name, descriptor))
+ }
+
+ return super.visitMethod(access, name, descriptor, signature, exceptions)
+ }
+
+ private data class JNIMethod(
+ val name: String,
+ val descriptor: String,
+ )
+}
+
+abstract class GenerateJNILinkTableTransformFactory :
+ AsmClassVisitorFactory {
+ override fun createClassVisitor(
+ classContext: ClassContext,
+ nextClassVisitor: ClassVisitor
+ ): ClassVisitor {
+ return GenerateJNILinkTableTransform(
+ Opcodes.ASM7,
+ nextClassVisitor,
+ parameters.get().outputDirectory.get()
+ )
+ }
+
+ override fun isInstrumentable(classData: ClassData): Boolean {
+ val className = classData.className
+ val packagePrefix = parameters.get().packagePrefix.get()
+
+ // Check if the class is in the specified package
+ return className.startsWith(packagePrefix)
+ }
+}
+
+interface GenerateJNILinkTableParameters : InstrumentationParameters {
+ @get:Input
+ val packagePrefix: Property
+
+ @get:OutputDirectory
+ val outputDirectory: DirectoryProperty
+}
diff --git a/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt
new file mode 100644
index 0000000000..066f874171
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/bugsnag/android/gradle/JNILinkTablePlugin.kt
@@ -0,0 +1,79 @@
+package com.bugsnag.android.gradle
+
+import com.android.build.api.instrumentation.InstrumentationScope
+import com.android.build.api.variant.AndroidComponentsExtension
+import com.android.build.api.variant.Variant
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.provider.Provider
+
+/**
+ * Generates C header files for use with [RegisterNatives](docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives)
+ * for the Bugsnag Android SDK. This plugin ensures that the Java/Kotlin classes with native methods
+ * have corresponding C functions as part of the build process. When the headers are included
+ * in the C or C++ code, any missing implementations will cause a build error.
+ */
+class JNILinkTablePlugin : Plugin {
+ /*
+ * This plugin generates JNI headers for the Bugsnag Android SDK by registering an
+ * identity ASM transformer to AGP. The transformer processes the classes, but does not
+ * modify them. Instead it just captures the native method signatures to generate the headers.
+ */
+
+ override fun apply(project: Project) {
+ // Create a directory for generated headers
+ val generatedHeadersDir = project.layout.buildDirectory.dir("generated/jni-headers")
+
+ // Get the Android components extension
+ val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
+
+ // Configure each variant
+ androidComponents.onVariants { variant ->
+ project.configureVariant(variant, generatedHeadersDir)
+ }
+ }
+
+ private fun Project.configureVariant(
+ variant: Variant,
+ generatedHeadersDir: Provider
+ ) {
+ val externalNativeBuild = variant.externalNativeBuild ?: return
+
+ // configure the variant to generate the headers from the class files
+ variant.transformClassesWith(
+ GenerateJNILinkTableTransformFactory::class.java,
+ InstrumentationScope.PROJECT
+ ) { parameters ->
+ parameters.packagePrefix.set("com.bugsnag.android")
+ parameters.outputDirectory.set(generatedHeadersDir)
+ }
+
+ fixTaskOrdering(variant)
+
+ // Get the physical directory
+ val headersDir = generatedHeadersDir.get().asFile
+
+ // Ensure the directory exists
+ headersDir.mkdirs()
+
+ // Configure CMake to include our generated headers directory
+ externalNativeBuild.cFlags.apply {
+ // Add the include directory to the CMake arguments
+ add("-I${headersDir.absolutePath}")
+ }
+ }
+
+ private fun Project.fixTaskOrdering(variant: Variant) {
+ val variantName = variant.name.capitalize()
+ val transformTaskName = "transform${variantName}ClassesWithAsm"
+ // Ensure the CMake task runs after the classes are transformed
+ tasks.whenTaskAdded {
+ when (name) {
+ "buildCMakeDebug", "buildCMakeRelWithDebInfo" -> {
+ dependsOn(transformTaskName)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 08903779c8..9ad40dc121 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,6 +16,8 @@ x-common-environment: &common-environment
BUILDKITE_RETRY_COUNT:
BUILDKITE_STEP_KEY:
MAZE_BUGSNAG_API_KEY:
+ MAZE_REPEATER_API_KEY:
+ MAZE_HUB_REPEATER_API_KEY:
MAZE_NO_FAIL_FAST:
MAZE_APPIUM_BUGSNAG_API_KEY:
MAZE_SCENARIO_BUGSNAG_API_KEY:
diff --git a/gradle.properties b/gradle.properties
index 1706d5d1ef..3a15499332 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx4096m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
-VERSION_NAME=6.14.0
+VERSION_NAME=6.15.0
GROUP=com.bugsnag
POM_SCM_URL=https://github.com/bugsnag/bugsnag-android
POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git
diff --git a/gradle/release.gradle b/gradle/release.gradle
index b2ba076769..3b2ab823b8 100644
--- a/gradle/release.gradle
+++ b/gradle/release.gradle
@@ -35,11 +35,8 @@ project.afterEvaluate {
repositories {
maven {
- if (VERSION_NAME.contains("SNAPSHOT")) {
- url "https://oss.sonatype.org/content/repositories/snapshots/"
- } else {
- url "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
- }
+ name = 'ossrhStaging'
+ url = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
credentials {
username = project.hasProperty("NEXUS_USERNAME") ? "$NEXUS_USERNAME" : System.getenv("NEXUS_USERNAME")
password = project.hasProperty("NEXUS_PASSWORD") ? "$NEXUS_PASSWORD" : System.getenv("NEXUS_PASSWORD")
diff --git a/scripts/docker-publish.sh b/scripts/docker-publish.sh
index 78be123a92..409d8dada3 100755
--- a/scripts/docker-publish.sh
+++ b/scripts/docker-publish.sh
@@ -10,5 +10,48 @@ echo "NEXUS_PASSWORD=$PUBLISH_PASS" >> ~/.gradle/gradle.properties
echo "nexusUsername=$PUBLISH_USER" >> ~/.gradle/gradle.properties
echo "nexusPassword=$PUBLISH_PASS" >> ~/.gradle/gradle.properties
-/app/gradlew assembleRelease publish --no-daemon --max-workers=1 && \
- echo "Go to https://oss.sonatype.org/ to release the final artefact. For the full release instructions, please read https://github.com/bugsnag/bugsnag-android/blob/next/docs/RELEASING.md"
+/app/gradlew assembleRelease publish --no-daemon --max-workers=1
+
+# === Close Staging Repository ===
+echo "--- Closing staging repository"
+echo "Fetching staging repositories..."
+
+REPOS_JSON=$(curl -s -u "$PUBLISH_USER:$PUBLISH_PASS" \
+ "https://ossrh-staging-api.central.sonatype.com/manual/search/repositories")
+
+if [[ -z "$REPOS_JSON" ]]; then
+ echo "Failed to retrieve repository list. Check your credentials or network." >&2
+ exit 1
+fi
+
+REPO_KEYS=($(echo "$REPOS_JSON" | jq -r '.repositories[] | select(.state == "open") | .key'))
+
+if [[ "${#REPO_KEYS[@]}" -eq 0 ]]; then
+ echo "No open repositories found."
+ exit 1
+elif [[ "${#REPO_KEYS[@]}" -gt 1 ]]; then
+ echo "Multiple open repositories found. Please specify which one to close:"
+ printf '%s\n' "${REPO_KEYS[@]}"
+ exit 1
+fi
+
+REPO_KEY="${REPO_KEYS[0]}"
+echo "Closing repository $REPO_KEY..."
+
+URL="https://ossrh-staging-api.central.sonatype.com/manual/upload/repository/$REPO_KEY?publishing_type=user_managed"
+RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST -u "$PUBLISH_USER:$PUBLISH_PASS" "$URL")
+
+BODY=$(echo "$RESPONSE" | sed -n '/^HTTP_STATUS:/!p')
+STATUS=$(echo "$RESPONSE" | sed -n 's/^HTTP_STATUS://p')
+
+if [[ "$STATUS" != "200" ]]; then
+ echo "Failed to close repository. HTTP Status: $STATUS"
+ echo "$BODY" | jq -r
+ exit 1
+fi
+
+echo "Repository $REPO_KEY closed successfully."
+
+echo "Go to https://oss.sonatype.org/ to release the final artefact."
+echo "For full release instructions, visit:"
+echo "https://github.com/bugsnag/bugsnag-android/blob/next/docs/RELEASING.md"