Skip to content

Commit 06186ac

Browse files
authored
Merge pull request #2235 from bugsnag/PLAT-14598/exitinfo-avoid-unmatched
ExitInfo Plugin: Avoid synthesizing duplicate ANRs when in fallback modes
2 parents db690a0 + 2cbfdb2 commit 06186ac

17 files changed

Lines changed: 669 additions & 304 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
* `memoryTrimLevel` and `lowMemory` will not be reported when they are not set (newer versions of Android do not set these values)
88
[#2237](https://github.com/bugsnag/bugsnag-android/pull/2237)
99

10+
### Bug fixes
11+
12+
* Handle edge-cases to reduce the changes of duplicate ANR reporting from `bugsnag-plugin-android-exitinfo` when `reportUnmatchedANR = true && disableProcessStateSummaryOverride = true`
13+
[#2235](https://github.com/bugsnag/bugsnag-android/pull/2235)
14+
1015
## 6.16.0 (2025-07-31)
1116

1217
### Changes

bugsnag-plugin-android-exitinfo/detekt-baseline.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
<SmellBaseline>
33
<ManuallySuppressedIssues/>
44
<CurrentIssues>
5-
<ID>CyclomaticComplexMethod:CodeStrings.kt$@RequiresApi(Build.VERSION_CODES.R) @SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") internal fun importanceDescriptionOf(exitInfo: ApplicationExitInfo)</ID>
6-
<ID>CyclomaticComplexMethod:CodeStrings.kt$@RequiresApi(Build.VERSION_CODES.R) internal fun exitReasonOf(exitInfo: ApplicationExitInfo)</ID>
5+
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) @SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") internal fun importanceDescriptionOf(exitInfo: ApplicationExitInfo)</ID>
6+
<ID>CyclomaticComplexMethod:CodeDescriptions.kt$@RequiresApi(Build.VERSION_CODES.R) internal fun exitReasonOf(exitInfo: ApplicationExitInfo)</ID>
77
<ID>LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit )</ID>
88
<ID>MagicNumber:TraceParser.kt$TraceParser$16</ID>
99
<ID>MagicNumber:TraceParser.kt$TraceParser$3</ID>
10-
<ID>MaxLineLength:ExitInfoCallbackTest.kt$ExitInfoCallbackTest$exitInfoCallback = ExitInfoCallback(context, nativeEnhancer, anrEventEnhancer, null, ApplicationExitInfoMatcher(context, 100))</ID>
1110
<ID>MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so "</ID>
1211
<ID>MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so (Java_com_example_bugsnag_android_BaseCrashyActivity_anrFromCXX+20"</ID>
1312
<ID>MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so (Java_com_example_bugsnag_android_BaseCrashyActivity_anrFromCXX+20) ("</ID>

bugsnag-plugin-android-exitinfo/src/androidTest/java/com/bugsnag/android/BugsnagExitInfoPluginStoreTest.kt

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.bugsnag.android
2+
3+
import org.junit.After
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Assert.assertNotNull
6+
import org.junit.Before
7+
import org.junit.Test
8+
import org.mockito.Mockito.mock
9+
import java.io.File
10+
11+
class ExitInfoPluginStoreTest {
12+
private lateinit var storeFile: File
13+
14+
@Before
15+
fun setUp() {
16+
storeFile = File.createTempFile("bugsnag-exit-reasons", null)
17+
}
18+
19+
@After
20+
fun tearDown() {
21+
storeFile.delete()
22+
}
23+
24+
@Test
25+
fun testStoreInitialization() {
26+
val store = ExitInfoPluginStore(storeFile, mock())
27+
28+
// Verify that the store initializes correctly
29+
assert(store.previousState == null)
30+
assert(store.currentState.processedExitInfoKeys.isEmpty())
31+
assert(store.currentState.pid == android.os.Process.myPid())
32+
assert(store.currentState.timestamp > 0)
33+
}
34+
35+
@Test
36+
fun testParsesLegacyFile() {
37+
storeFile.writeText("1234567890")
38+
val store = ExitInfoPluginStore(storeFile, mock())
39+
40+
assertEquals(1234567890, store.previousState?.pid)
41+
assertEquals(true, store.previousState?.processedExitInfoKeys?.isEmpty())
42+
}
43+
44+
@Test
45+
fun testPersistsExitInfoKeys() {
46+
val store = ExitInfoPluginStore(storeFile, mock())
47+
store.addExitInfoKey(ExitInfoKey(1234, 1234567890L))
48+
store.addExitInfoKey(ExitInfoKey(5678, 1234567891L))
49+
50+
val restoredStore = ExitInfoPluginStore(storeFile, mock())
51+
assertEquals(2, restoredStore.previousState?.processedExitInfoKeys?.size)
52+
val previousState = restoredStore.previousState
53+
assertNotNull(previousState)
54+
assertEquals(
55+
true,
56+
previousState?.processedExitInfoKeys?.contains(ExitInfoKey(1234, 1234567890L))
57+
)
58+
assertEquals(
59+
true,
60+
previousState?.processedExitInfoKeys?.contains(ExitInfoKey(5678, 1234567891L))
61+
)
62+
}
63+
}

bugsnag-plugin-android-exitinfo/src/androidTest/java/com/bugsnag/android/TestHooks.java

Lines changed: 0 additions & 17 deletions
This file was deleted.

bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ApplicationExitInfoMatcher.kt

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
package com.bugsnag.android
22

3-
import android.app.ActivityManager
43
import android.app.ApplicationExitInfo
5-
import android.content.Context
64
import android.os.Build
75
import androidx.annotation.RequiresApi
86

97
@RequiresApi(Build.VERSION_CODES.R)
108
internal class ApplicationExitInfoMatcher(
11-
private val context: Context,
12-
private val pid: Int
9+
private val applicationExitInfo: List<ApplicationExitInfo>,
10+
private val previousState: ExitInfoPluginStore.PersistentState? = null,
1311
) {
1412
fun matchExitInfo(event: Event): ApplicationExitInfo? {
15-
val am: ActivityManager =
16-
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
17-
val allExitInfo: List<ApplicationExitInfo> =
18-
am.getHistoricalProcessExitReasons(context.packageName, MATCH_ALL, MAX_EXIT_INFO)
19-
val sessionIdBytes: ByteArray =
20-
event.session?.id?.toByteArray() ?: return null
21-
val exitInfo: ApplicationExitInfo =
22-
findExitInfoBySessionId(allExitInfo, sessionIdBytes)
23-
?: findExitInfoByPid(allExitInfo) ?: return null
24-
return exitInfo
13+
val sessionIdBytes: ByteArray = event.session?.id?.toByteArray() ?: return null
14+
return findExitInfoBySessionId(applicationExitInfo, sessionIdBytes)
15+
?: findExitInfoByPid(applicationExitInfo)
2516
}
2617

2718
internal fun findExitInfoBySessionId(
@@ -32,7 +23,7 @@ internal class ApplicationExitInfoMatcher(
3223
}
3324

3425
internal fun findExitInfoByPid(allExitInfo: List<ApplicationExitInfo>) =
35-
allExitInfo.find { it.pid == pid }
26+
allExitInfo.find { it.pid == previousState?.pid }
3627

3728
internal companion object {
3829
const val MATCH_ALL = 0

0 commit comments

Comments
 (0)