diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c4ebf583..18b283d84b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ * Prevent rare app crash while migrating old `SharedPreferences` data from older versions of `bugsnag-android` [#1860](https://github.com/bugsnag/bugsnag-android/pull/1860) +* Prevent free memory calculation from potentially crashing the app when `ActivityManager` cannot be reached. + [#1861](https://github.com/bugsnag/bugsnag-android/pull/1861) + ## 5.30.0 (2023-05-11) ### Enhancements diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 0342851daf..fb7824ddb0 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -45,6 +45,8 @@ SwallowedException:BugsnagEventMapper.kt$BugsnagEventMapper$catch (pe: IllegalArgumentException) { ndkDateFormatHolder.get()!!.parse(this) ?: throw IllegalArgumentException("cannot parse date $this") } SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null } SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null } + SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (e: Throwable) { null } + SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (e: Throwable) { return null } SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false } SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") } SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt index a7755a2edc..493763a493 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt @@ -242,21 +242,26 @@ internal class DeviceDataCollector( /** * Get the amount of memory remaining on the device */ - private fun calculateFreeMemory(): Long? { + fun calculateFreeMemory(): Long? { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - val freeMemory = appContext.getActivityManager() - ?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } } - ?.availMem - - if (freeMemory != null) { - return freeMemory + try { + val freeMemory = appContext.getActivityManager() + ?.let { am -> ActivityManager.MemoryInfo().also { am.getMemoryInfo(it) } } + ?.availMem + if (freeMemory != null) { + return freeMemory + } + } catch (e: Throwable) { + return null } } - return runCatching { + return try { @Suppress("PrivateApi") AndroidProcess::class.java.getDeclaredMethod("getFreeMemory").invoke(null) as Long? - }.getOrNull() + } catch (e: Throwable) { + null + } } /** diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DataCollectorTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt similarity index 64% rename from bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DataCollectorTest.kt rename to bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt index 443bf353c6..307151bc20 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DataCollectorTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt @@ -4,24 +4,34 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import com.bugsnag.android.internal.BackgroundTaskService -import org.junit.Ignore +import org.junit.Assert +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner import java.io.File import kotlin.concurrent.thread -@RunWith(MockitoJUnitRunner::class) +@RunWith(MockitoJUnitRunner.Silent::class) class DataCollectorTest { - @Ignore("Disabled until we're able to mock final classes or auto-open classes") - @Test - fun testConcurretAccess() { + private lateinit var collector: DeviceDataCollector + + @Mock + lateinit var context: Context + + @Mock + lateinit var logger: Logger + + @Before + fun setUp() { val res = Mockito.mock(Resources::class.java) - Mockito.`when`(res.configuration).thenReturn(Configuration()) + `when`(res.configuration).thenReturn(Configuration()) - val collector = DeviceDataCollector( + collector = DeviceDataCollector( Mockito.mock(Connectivity::class.java), Mockito.mock(Context::class.java), res, @@ -33,7 +43,17 @@ class DataCollectorTest { Mockito.mock(BackgroundTaskService::class.java), Mockito.mock(Logger::class.java) ) + } + @Test + fun testCalculateFreeMemoryWithException() { + `when`(collector.calculateFreeMemory()).thenThrow(RuntimeException()) + collector.generateDeviceWithState(0) + Assert.assertNull(collector.calculateFreeMemory()) + } + + @Test + fun testConcurrentAccess() { repeat(10) { index -> collector.addRuntimeVersionInfo("key" + index, "value" + index) }