Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
[#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)

Expand Down
3 changes: 2 additions & 1 deletion bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ID>CyclomaticComplexMethod:ConfigInternal.kt$ConfigInternal$fun getConfigDifferences(): Map&lt;String, Any></ID>
<ID>ImplicitDefaultLocale:Deliverable.kt$Deliverable$String.format("%02x", byte)</ID>
<ID>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&lt;String?>?, /** * The application type set in [Configuration#version] */ var type: String?, /** * The version code of the application set in [Configuration.versionCode] */ var versionCode: Number? )</ID>
<ID>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 )</ID>
<ID>LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: Provider&lt;SessionTracker>, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: Provider&lt;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? )</ID>
<ID>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? )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )</ID>
Expand Down Expand Up @@ -61,6 +61,7 @@
<ID>SwallowedException:ForegroundDetector.kt$ForegroundDetector$e: Exception</ID>
<ID>SwallowedException:JsonHelperTest.kt$JsonHelperTest$e: IllegalArgumentException</ID>
<ID>SwallowedException:PluginClient.kt$PluginClient$exc: ClassNotFoundException</ID>
<ID>SwallowedException:RootDetector.kt$RootDetector$ex: IllegalThreadStateException</ID>
<ID>SwallowedException:SharedPrefMigrator.kt$SharedPrefMigrator$e: RuntimeException</ID>
<ID>ThrowsCount:JsonHelper.kt$JsonHelper$fun jsonToLong(value: Any?): Long?</ID>
<ID>TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,7 +42,7 @@ class AppDataCollectorForegroundTest {
appContext,
null,
config,
sessionTracker,
ValueProvider(sessionTracker),
null,
launchCrashTracker,
memoryTrimState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,7 +50,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand All @@ -70,7 +71,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand All @@ -93,7 +94,7 @@ class AppDataCollectorTest {
context,
context.packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand All @@ -115,7 +116,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand Down Expand Up @@ -144,7 +145,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand All @@ -171,7 +172,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand All @@ -198,7 +199,7 @@ class AppDataCollectorTest {
context,
packageManager,
client.immutableConfig,
client.sessionTracker,
ValueProvider(client.sessionTracker),
am,
client.launchCrashTracker,
client.memoryTrimState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<SessionTracker>,
private val activityManager: ActivityManager?,
private val launchCrashTracker: LaunchCrashTracker,
private val memoryTrimState: MemoryTrimState
Expand All @@ -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(
Expand Down Expand Up @@ -118,7 +119,7 @@ internal class AppDataCollector(
fun getAppDataMetadata(): MutableMap<String, Any?> {
val map = HashMap<String, Any?>()
map["name"] = appName
map["activeScreen"] = sessionTracker.contextActivity
map["activeScreen"] = sessionTracker.get().contextActivity
map["lowMemory"] = memoryTrimState.isLowMemory
map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription
map["processImportance"] = getProcessImportance()
Expand Down Expand Up @@ -168,15 +169,15 @@ 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
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal class DataCollectionModule(
ctx,
ctx.packageManager,
cfg,
trackerModule.sessionTracker.get(),
trackerModule.sessionTracker,
systemServiceModule.activityManager,
trackerModule.launchCrashTracker,
memoryTrimState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,7 +43,7 @@ internal class EventStorageModule(
cfg.logger,
notifier,
bgTaskService,
delegate.getOrNull(),
delegate,
callbackState
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,7 +28,7 @@ internal class EventStore(
logger: Logger,
notifier: Notifier,
bgTaskService: BackgroundTaskService,
delegate: Delegate?,
delegate: Provider<out Delegate?>?,
callbackState: CallbackState
) : FileStore(
File(config.persistenceDirectory.value, "bugsnag/errors"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<out Delegate?>?
) {
internal fun interface Delegate {
/**
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ class InternalReportDelegate implements EventStore.Delegate {
@Nullable
final StorageManager storageManager;

final AppDataCollector appDataCollector;
final Provider<AppDataCollector> appDataCollector;
final Provider<DeviceDataCollector> deviceDataCollector;
final Context appContext;
final SessionTracker sessionTracker;
final Provider<SessionTracker> sessionTracker;
final Notifier notifier;
final BackgroundTaskService backgroundTaskService;

InternalReportDelegate(Context context,
Logger logger,
ImmutableConfig immutableConfig,
@Nullable StorageManager storageManager,
AppDataCollector appDataCollector,
Provider<AppDataCollector> appDataCollector,
Provider<DeviceDataCollector> deviceDataCollector,
SessionTracker sessionTracker,
Provider<SessionTracker> sessionTracker,
Notifier notifier,
BackgroundTaskService backgroundTaskService) {
this.logger = logger;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,7 +17,7 @@ internal class SessionStore(
maxPersistedSessions: Int,
private val apiKey: String,
logger: Logger,
delegate: Delegate?
delegate: Provider<out Delegate?>?
) : FileStore(
File(bugsnagDir, "sessions"),
maxPersistedSessions,
Expand Down
Loading
Loading