From 0806942d46f2a621ed914c7891eafe3747e3c81e Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Aug 2021 15:10:20 +0100 Subject: [PATCH] feat(memoryTrimLevel): gather the `app.memoryTrimLevel` metadata and include it in reports --- CHANGELOG.md | 3 ++ bugsnag-android-core/detekt-baseline.xml | 5 ++- .../bugsnag/android/AppDataCollectorTest.kt | 9 ++++-- .../java/com/bugsnag/android/ClientTest.java | 3 +- .../com/bugsnag/android/AppDataCollector.kt | 12 +++---- .../main/java/com/bugsnag/android/Client.java | 17 +++++++--- .../android/ClientComponentCallbacks.kt | 6 ++-- .../com/bugsnag/android/ClientObservable.kt | 4 --- .../bugsnag/android/DataCollectionModule.kt | 6 ++-- .../com/bugsnag/android/MemoryTrimState.kt | 32 +++++++++++++++++++ .../java/com/bugsnag/android/StateEvent.kt | 6 +++- .../android/AppDataCollectorForegroundTest.kt | 6 +++- .../AppDataCollectorSerializationTest.kt | 4 ++- .../android/AppMetadataSerializationTest.kt | 4 ++- ...entComponentCallbacksMemoryCallbackTest.kt | 9 ++++-- .../android/ConfigChangeReceiverTest.kt | 20 ++++++++++-- .../app_meta_data_serialization_0.json | 1 + .../com/bugsnag/android/ndk/NativeBridge.kt | 4 +-- .../src/main/jni/bugsnag_ndk.c | 22 ++++++++++--- features/smoke_tests/handled.feature | 1 + 20 files changed, 130 insertions(+), 44 deletions(-) create mode 100644 bugsnag-android-core/src/main/java/com/bugsnag/android/MemoryTrimState.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index c007915b4d..4e9345e658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * The `app.lowMemory` value always report the most recent `onTrimMemory`/`onLowMemory` status [#1342](https://github.com/bugsnag/bugsnag-android/pull/1342) + +* Added the `app.memoryTrimLevel` metadata to report a description of the latest `onTrimMemory` status + [#1344](https://github.com/bugsnag/bugsnag-android/pull/1344) ## 5.11.0 (2021-08-05) diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index e1d5315b0d..3dff843d88 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -4,10 +4,10 @@ ImplicitDefaultLocale:DeliveryHeaders.kt$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] */ var buildUuid: 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 logger: Logger ) + 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: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 */ 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$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? ) - LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String? ) + LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String?, memoryTrimState: MemoryTrimState ) LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array<String>?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ var runtimeVersions: MutableMap<String, Any>? ) LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array<String>? ) LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger ) @@ -27,7 +27,6 @@ ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityInternal(severity: Severity) ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityReason(@SeverityReason.SeverityReasonType reason: String) ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, streamable: JsonStream.Streamable, headers: Map<String, String?> ): DeliveryStatus - SwallowedException:AppDataCollector.kt$AppDataCollector$catch (exception: Exception) { logger.w("Could not check lowMemory status") } 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 (exc: Exception) { false } 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 4d7317c319..8120f1074f 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 @@ -47,7 +47,8 @@ class AppDataCollectorTest { client.immutableConfig, client.sessionTracker, am, - client.launchCrashTracker + client.launchCrashTracker, + client.memoryTrimState ) val app = collector.getAppDataMetadata() assertNull(app["backgroundWorkRestricted"]) @@ -66,7 +67,8 @@ class AppDataCollectorTest { client.immutableConfig, client.sessionTracker, am, - client.launchCrashTracker + client.launchCrashTracker, + client.memoryTrimState ) client.context = "Some Custom Context" client.sessionTracker.updateForegroundTracker("MyActivity", true, 0L) @@ -88,7 +90,8 @@ class AppDataCollectorTest { client.immutableConfig, client.sessionTracker, am, - client.launchCrashTracker + client.launchCrashTracker, + client.memoryTrimState ) val app = collector.getAppDataMetadata() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientTest.java b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientTest.java index deb1e9ccc2..06599b569a 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientTest.java +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientTest.java @@ -170,12 +170,13 @@ public void testAppDataCollection() { public void testAppDataMetadata() { client = generateClient(); Map app = client.getAppDataCollector().getAppDataMetadata(); - assertEquals(5, app.size()); + assertEquals(6, app.size()); assertEquals("Bugsnag Android Tests", app.get("name")); assertEquals("com.bugsnag.android.core.test", app.get("processName")); assertNotNull(app.get("memoryUsage")); assertTrue(app.containsKey("activeScreen")); assertNotNull(app.get("lowMemory")); + assertNotNull(app.get("memoryTrimLevel")); } @Test 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 9149d54f9c..cf99b540bb 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 @@ -19,13 +19,10 @@ internal class AppDataCollector( private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, - private val launchCrashTracker: LaunchCrashTracker + private val launchCrashTracker: LaunchCrashTracker, + private val memoryTrimState: MemoryTrimState ) { - /** - * Is the app considered to be an a low-memory state as defined by - * [android.content.ComponentCallbacks] - */ - var isLowMemory: Boolean = false + var codeBundleId: String? = null private val packageName: String = appContext.packageName @@ -56,7 +53,8 @@ internal class AppDataCollector( map["name"] = appName map["activeScreen"] = sessionTracker.contextActivity map["memoryUsage"] = getMemoryUsage() - map["lowMemory"] = isLowMemory + map["lowMemory"] = memoryTrimState.isLowMemory + map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription bgWorkRestricted?.let { map["backgroundWorkRestricted"] = bgWorkRestricted diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java index ee8a56167e..2d1083d913 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java @@ -62,6 +62,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware { @NonNull final BreadcrumbState breadcrumbState; + @NonNull + final MemoryTrimState memoryTrimState = new MemoryTrimState(); + @NonNull protected final EventStore eventStore; @@ -162,7 +165,7 @@ public Unit invoke(Boolean hasConnection, String networkState) { DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule, configModule, systemServiceModule, trackerModule, - bgTaskService, connectivity, storageModule.getDeviceId()); + bgTaskService, connectivity, storageModule.getDeviceId(), memoryTrimState); dataCollectionModule.resolveDependencies(bgTaskService, TaskType.IO); appDataCollector = dataCollectionModule.getAppDataCollector(); deviceDataCollector = dataCollectionModule.getDeviceDataCollector(); @@ -337,11 +340,12 @@ public Unit invoke(String oldOrientation, String newOrientation) { clientObservable.postOrientationChange(newOrientation); return null; } - }, new Function1() { + }, new Function2() { @Override - public Unit invoke(Boolean isLowMemory) { - appDataCollector.setLowMemory(Boolean.TRUE.equals(isLowMemory)); - clientObservable.postMemoryTrimEvent(isLowMemory); + public Unit invoke(Boolean isLowMemory, Integer memoryTrimLevel) { + memoryTrimState.setLowMemory(Boolean.TRUE.equals(isLowMemory)); + memoryTrimState.setMemoryTrimLevel(memoryTrimLevel); + memoryTrimState.emitObservableEvent(); return null; } } @@ -384,6 +388,7 @@ void addObserver(StateObserver observer) { contextState.addObserver(observer); deliveryDelegate.addObserver(observer); launchCrashTracker.addObserver(observer); + memoryTrimState.addObserver(observer); } void removeObserver(StateObserver observer) { @@ -395,6 +400,7 @@ void removeObserver(StateObserver observer) { contextState.removeObserver(observer); deliveryDelegate.removeObserver(observer); launchCrashTracker.removeObserver(observer); + memoryTrimState.removeObserver(observer); } /** @@ -404,6 +410,7 @@ void syncInitialState() { metadataState.emitObservableEvent(); contextState.emitObservableEvent(); userState.emitObservableEvent(); + memoryTrimState.emitObservableEvent(); } /** diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientComponentCallbacks.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientComponentCallbacks.kt index 4c1def2a97..d4d97d1df3 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientComponentCallbacks.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientComponentCallbacks.kt @@ -6,7 +6,7 @@ import android.content.res.Configuration internal class ClientComponentCallbacks( private val deviceDataCollector: DeviceDataCollector, private val cb: (oldOrientation: String?, newOrientation: String?) -> Unit, - val callback: (Boolean) -> Unit + val memoryCallback: (Boolean, Int?) -> Unit ) : ComponentCallbacks2 { override fun onConfigurationChanged(newConfig: Configuration) { @@ -19,10 +19,10 @@ internal class ClientComponentCallbacks( } override fun onTrimMemory(level: Int) { - callback(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) + memoryCallback(level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE, level) } override fun onLowMemory() { - callback(true) + memoryCallback(true, null) } } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt index 8bd8e47522..d96724c100 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt @@ -8,10 +8,6 @@ internal class ClientObservable : BaseObservable() { updateState { StateEvent.UpdateOrientation(orientation) } } - fun postMemoryTrimEvent(isLowMemory: Boolean) { - updateState { StateEvent.UpdateMemoryTrimEvent(isLowMemory) } - } - fun postNdkInstall( conf: ImmutableConfig, lastRunInfoPath: String, 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 71b434b278..577b9bf7bc 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 @@ -17,7 +17,8 @@ internal class DataCollectionModule( trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, - deviceId: String? + deviceId: String?, + memoryTrimState: MemoryTrimState ) : DependencyModule() { private val ctx = contextModule.ctx @@ -33,7 +34,8 @@ internal class DataCollectionModule( cfg, trackerModule.sessionTracker, systemServiceModule.activityManager, - trackerModule.launchCrashTracker + trackerModule.launchCrashTracker, + memoryTrimState ) } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/MemoryTrimState.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/MemoryTrimState.kt new file mode 100644 index 0000000000..51ee65f482 --- /dev/null +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/MemoryTrimState.kt @@ -0,0 +1,32 @@ +package com.bugsnag.android + +import android.content.ComponentCallbacks2 + +internal class MemoryTrimState : BaseObservable() { + var isLowMemory: Boolean = false + var memoryTrimLevel: Int? = null + + val trimLevelDescription: String get() = descriptionFor(memoryTrimLevel) + + fun emitObservableEvent() { + updateState { + StateEvent.UpdateMemoryTrimEvent( + isLowMemory, + memoryTrimLevel, + trimLevelDescription + ) + } + } + + private fun descriptionFor(memoryTrimLevel: Int?) = when (memoryTrimLevel) { + null -> "None" + ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> "Complete" + ComponentCallbacks2.TRIM_MEMORY_MODERATE -> "Moderate" + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND -> "Background" + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> "UI hidden" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> "Running critical" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW -> "Running low" + ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE -> "Running moderate" + else -> "Unknown ($memoryTrimLevel)" + } +} diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt index a94a0301fb..de6bb3963b 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/StateEvent.kt @@ -62,5 +62,9 @@ sealed class StateEvent { // JvmField allows direct field access optimizations class UpdateUser(@JvmField val user: User) : StateEvent() - class UpdateMemoryTrimEvent(@JvmField val isLowMemory: Boolean) : StateEvent() + class UpdateMemoryTrimEvent( + @JvmField val isLowMemory: Boolean, + @JvmField val memoryTrimLevel: Int? = null, + @JvmField val memoryTrimLevelDescription: String = "None" + ) : StateEvent() } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt index de778dbf46..8e005b4943 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/AppDataCollectorForegroundTest.kt @@ -29,6 +29,9 @@ class AppDataCollectorForegroundTest { @Mock internal lateinit var launchCrashTracker: LaunchCrashTracker + @Mock + internal lateinit var memoryTrimState: MemoryTrimState + private lateinit var appDataCollector: AppDataCollector @Before @@ -41,7 +44,8 @@ class AppDataCollectorForegroundTest { BugsnagTestUtils.generateImmutableConfig(), sessionTracker, null, - launchCrashTracker + launchCrashTracker, + memoryTrimState ) } 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 c390a5e219..3273c9c9ac 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 @@ -28,6 +28,7 @@ internal class AppDataCollectorSerializationTest { val sessionTracker = mock(SessionTracker::class.java) val launchCrashTracker = mock(LaunchCrashTracker::class.java) val config = BugsnagTestUtils.generateConfiguration() + val memoryTrimState = MemoryTrimState() // populate summary fields config.appType = "React Native" @@ -50,7 +51,8 @@ internal class AppDataCollectorSerializationTest { convert(config), sessionTracker, am, - launchCrashTracker + launchCrashTracker, + memoryTrimState ) appData.codeBundleId = "foo-99" appData.setBinaryArch("x86") 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 a6e72e0e9f..15cb9d2a32 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 @@ -28,6 +28,7 @@ internal class AppMetadataSerializationTest { val sessionTracker = mock(SessionTracker::class.java) val launchCrashTracker = mock(LaunchCrashTracker::class.java) val config = BugsnagTestUtils.generateConfiguration() + val memoryTrimState = MemoryTrimState() // populate summary fields config.appType = "React Native" @@ -50,7 +51,8 @@ internal class AppMetadataSerializationTest { convertToImmutableConfig(config, null, null, ApplicationInfo()), sessionTracker, am, - launchCrashTracker + launchCrashTracker, + memoryTrimState ) appData.codeBundleId = "foo-99" appData.setBinaryArch("x86") diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientComponentCallbacksMemoryCallbackTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientComponentCallbacksMemoryCallbackTest.kt index 13d12a7362..ee343dfb21 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientComponentCallbacksMemoryCallbackTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientComponentCallbacksMemoryCallbackTest.kt @@ -1,6 +1,7 @@ package com.bugsnag.android import android.content.ComponentCallbacks2 +import android.content.Context import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -11,6 +12,9 @@ import org.mockito.junit.MockitoJUnitRunner @RunWith(MockitoJUnitRunner::class) class ClientComponentCallbacksMemoryCallbackTest { + @Mock + lateinit var context: Context + @Mock internal lateinit var deviceDataCollector: DeviceDataCollector private lateinit var clientComponentCallbacks: ClientComponentCallbacks @@ -21,9 +25,8 @@ class ClientComponentCallbacksMemoryCallbackTest { fun setUp() { clientComponentCallbacks = ClientComponentCallbacks( deviceDataCollector, - { _: String?, _: String? -> }, - this::isLowMemory::set - ) + { _: String?, _: String? -> } + ) { lowMemory, _ -> isLowMemory = lowMemory } isLowMemory = null } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt index 3f28daf4f4..ec3470d393 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt @@ -44,6 +44,8 @@ internal class ConfigChangeReceiverTest { @Mock lateinit var bgTaskService: BackgroundTaskService + private val memoryCallback: (Boolean, Int?) -> Unit = { _, _ -> } + private fun createDeviceDataCollector(): DeviceDataCollector = DeviceDataCollector( connectivity, appContext, @@ -92,7 +94,11 @@ internal class ConfigChangeReceiverTest { assertEquals("portrait", new) } config.orientation = Configuration.ORIENTATION_UNDEFINED - val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {}) + val callbacks = ClientComponentCallbacks( + createDeviceDataCollector(), + orientationCb, + memoryCallback + ) configUpdate.orientation = Configuration.ORIENTATION_PORTRAIT callbacks.onConfigurationChanged(configUpdate) @@ -106,7 +112,11 @@ internal class ConfigChangeReceiverTest { assertEquals("landscape", new) } config.orientation = Configuration.ORIENTATION_PORTRAIT - val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {}) + val callbacks = ClientComponentCallbacks( + createDeviceDataCollector(), + orientationCb, + memoryCallback + ) configUpdate.orientation = Configuration.ORIENTATION_LANDSCAPE callbacks.onConfigurationChanged(configUpdate) @@ -123,7 +133,11 @@ internal class ConfigChangeReceiverTest { } `when`(resources.configuration).thenReturn(config) config.orientation = Configuration.ORIENTATION_PORTRAIT - val callbacks = ClientComponentCallbacks(createDeviceDataCollector(), orientationCb, {}) + val callbacks = ClientComponentCallbacks( + createDeviceDataCollector(), + orientationCb, + memoryCallback + ) configUpdate.orientation = Configuration.ORIENTATION_PORTRAIT callbacks.onConfigurationChanged(configUpdate) diff --git a/bugsnag-android-core/src/test/resources/app_meta_data_serialization_0.json b/bugsnag-android-core/src/test/resources/app_meta_data_serialization_0.json index a4685d1592..df16b20abf 100644 --- a/bugsnag-android-core/src/test/resources/app_meta_data_serialization_0.json +++ b/bugsnag-android-core/src/test/resources/app_meta_data_serialization_0.json @@ -1,4 +1,5 @@ { + "memoryTrimLevel": "None", "activeScreen": "MyActivity", "name": "MyApp", "lowMemory": false 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 d65d2a9e5e..8bfd379555 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 @@ -79,7 +79,7 @@ class NativeBridge : StateObserver { external fun updateUserEmail(newValue: String) external fun updateUserName(newValue: String) external fun getUnwindStackFunction(): Long - external fun updateLowMemory(newValue: Boolean) + external fun updateLowMemory(newValue: Boolean, memoryTrimLevelDescription: String) override fun onStateChange(event: StateEvent) { if (isInvalidMessage(event)) return @@ -121,7 +121,7 @@ class NativeBridge : StateObserver { updateUserName(makeSafe(event.user.name ?: "")) updateUserEmail(makeSafe(event.user.email ?: "")) } - is StateEvent.UpdateMemoryTrimEvent -> updateLowMemory(event.isLowMemory) + is StateEvent.UpdateMemoryTrimEvent -> updateLowMemory(event.isLowMemory, event.memoryTrimLevelDescription) } } 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 9b85853764..3a7f334854 100644 --- a/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c +++ b/bugsnag-plugin-android-ndk/src/main/jni/bugsnag_ndk.c @@ -471,16 +471,30 @@ Java_com_bugsnag_android_ndk_NativeBridge_updateIsLaunching( } JNIEXPORT void JNICALL -Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory(JNIEnv *env, - jobject _this, - jboolean new_value) { +Java_com_bugsnag_android_ndk_NativeBridge_updateLowMemory( + JNIEnv *env, jobject _this, jboolean low_memory, + jstring memory_trim_level_description) { if (bsg_global_env == NULL) { return; } + + char *memory_trim_level = + (char *)bsg_safe_get_string_utf_chars(env, memory_trim_level_description); + + if (memory_trim_level == NULL) { + return; + } + bsg_request_env_write_lock(); bugsnag_event_add_metadata_bool(&bsg_global_env->next_event, "app", - "lowMemory", (bool)new_value); + "lowMemory", (bool)low_memory); + bugsnag_event_add_metadata_string(&bsg_global_env->next_event, "app", + "memoryTrimLevel", memory_trim_level); bsg_release_env_write_lock(); + if (memory_trim_level_description != NULL) { + bsg_safe_release_string_utf_chars(env, memory_trim_level_description, + memory_trim_level); + } } JNIEXPORT void JNICALL diff --git a/features/smoke_tests/handled.feature b/features/smoke_tests/handled.feature index 8da1e18ad3..d892bffd17 100644 --- a/features/smoke_tests/handled.feature +++ b/features/smoke_tests/handled.feature @@ -44,6 +44,7 @@ Scenario: Notify caught Java exception with default configuration And the error payload field "events.0.metaData.app.memoryUsage" is greater than 0 And the event "metaData.app.name" equals "MazeRunner" And the event "metaData.app.lowMemory" is false + And the event "metaData.app.memoryTrimLevel" equals "None" # Device data And the error payload field "events.0.device.cpuAbi" is a non-empty array