diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index bccf015f28..1435111433 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -2,6 +2,7 @@ + CyclomaticComplexMethod:AppDataCollector.kt$AppDataCollector$@SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") private fun getProcessImportance(): String? CyclomaticComplexMethod:ConfigInternal.kt$ConfigInternal$fun getConfigDifferences(): Map<String, Any> 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? ) 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 5847d1fe4e..09da647a6a 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 @@ -4,6 +4,7 @@ import android.app.ActivityManager import android.content.Context import android.content.pm.PackageManager import android.os.Build +import android.os.Process import androidx.test.core.app.ApplicationProvider import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -126,4 +127,84 @@ class AppDataCollectorTest { val result = collector.getInstallerPackageName() assertEquals("Test Installer name", result) } + + @Test + fun testGetProcessImportanceWithVersion29() = withBuildSdkInt(Build.VERSION_CODES.Q) { + val packageManager = mock(PackageManager::class.java) + `when`(packageManager.getApplicationLabel(any())).thenReturn("Test App name") + `when`(am.runningAppProcesses).thenReturn( + listOf( + ActivityManager.RunningAppProcessInfo().apply { + importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + } + ) + ) + + val collector = AppDataCollector( + context, + packageManager, + client.immutableConfig, + client.sessionTracker, + am, + client.launchCrashTracker, + client.memoryTrimState + ) + + val result = collector.getAppDataMetadata()["processImportance"] + assertEquals("foreground service", result) + } + + @Test + fun testGetProcessImportanceWithVersion14() = withBuildSdkInt(Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + val packageManager = mock(PackageManager::class.java) + `when`(packageManager.getApplicationLabel(any())).thenReturn("Test App name") + `when`(am.runningAppProcesses).thenReturn( + listOf( + ActivityManager.RunningAppProcessInfo().apply { + importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + pid = Process.myPid() + } + ) + ) + + val collector = AppDataCollector( + context, + packageManager, + client.immutableConfig, + client.sessionTracker, + am, + client.launchCrashTracker, + client.memoryTrimState + ) + + val result = collector.getAppDataMetadata()["processImportance"] + assertEquals("foreground", result) + } + + @Test + fun testGetProcessImportanceWithPid0() = withBuildSdkInt(Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + val packageManager = mock(PackageManager::class.java) + `when`(packageManager.getApplicationLabel(any())).thenReturn("Test App name") + `when`(am.runningAppProcesses).thenReturn( + listOf( + ActivityManager.RunningAppProcessInfo().apply { + importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + pid = 0 + } + ) + ) + + val collector = AppDataCollector( + context, + packageManager, + client.immutableConfig, + client.sessionTracker, + am, + client.launchCrashTracker, + client.memoryTrimState + ) + + val result = collector.getAppDataMetadata()["processImportance"] + assertEquals(null, result) + } } 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 8f5b84dc20..96cccb0bd7 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 @@ -171,7 +171,7 @@ public void testAppDataCollection() { public void testAppDataMetadata() { client = generateClient(); Map app = client.getAppDataCollector().getAppDataMetadata(); - assertEquals(10, app.size()); + assertEquals(11, app.size()); assertEquals("Bugsnag Android Tests", app.get("name")); assertEquals("com.bugsnag.android.core.test", app.get("processName")); assertNotNull(app.get("memoryUsage")); 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 ee23a07a72..a74579eea9 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 @@ -2,11 +2,26 @@ package com.bugsnag.android import android.annotation.SuppressLint import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING_PRE_28 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE import android.app.Application import android.content.Context import android.content.pm.PackageManager import android.os.Build.VERSION import android.os.Build.VERSION_CODES +import android.os.Process import android.os.SystemClock import com.bugsnag.android.internal.ImmutableConfig @@ -49,12 +64,57 @@ internal class AppDataCollector( ) } + @SuppressLint("SwitchIntDef") + @Suppress("DEPRECATION") + private fun getProcessImportance(): String? { + try { + val appInfo = ActivityManager.RunningAppProcessInfo() + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + ActivityManager.getMyMemoryState(appInfo) + } else { + val expectedPid = Process.myPid() + activityManager?.runningAppProcesses + ?.find { it.pid == expectedPid } + ?.let { + appInfo.importance = it.importance + appInfo.pid = expectedPid + } + } + + if (appInfo.pid == 0) { + return null + } + + return when (appInfo.importance) { + IMPORTANCE_FOREGROUND -> "foreground" + IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" + IMPORTANCE_TOP_SLEEPING -> "top sleeping" + IMPORTANCE_TOP_SLEEPING_PRE_28 -> "top sleeping" + IMPORTANCE_VISIBLE -> "visible" + IMPORTANCE_PERCEPTIBLE -> "perceptible" + IMPORTANCE_PERCEPTIBLE_PRE_26 -> "perceptible" + IMPORTANCE_CANT_SAVE_STATE -> "can't save state" + IMPORTANCE_CANT_SAVE_STATE_PRE_26 -> "can't save state" + IMPORTANCE_SERVICE -> "service" + IMPORTANCE_CACHED -> "cached/background" + IMPORTANCE_GONE -> "gone" + IMPORTANCE_EMPTY -> "empty" + REASON_PROVIDER_IN_USE -> "provider in use" + REASON_SERVICE_IN_USE -> "service in use" + else -> "unknown importance (${appInfo.importance})" + } + } catch (e: Exception) { + return null + } + } + fun getAppDataMetadata(): MutableMap { val map = HashMap() map["name"] = appName map["activeScreen"] = sessionTracker.contextActivity map["lowMemory"] = memoryTrimState.isLowMemory map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription + map["processImportance"] = getProcessImportance() populateRuntimeMemoryMetadata(map) @@ -128,6 +188,7 @@ internal class AppDataCollector( packageManager != null && copy != null -> { packageManager.getApplicationLabel(copy).toString() } + else -> null } } @@ -156,6 +217,7 @@ internal class AppDataCollector( VERSION.SDK_INT >= VERSION_CODES.P -> { Application.getProcessName() } + else -> { // see https://stackoverflow.com/questions/19631894 val clz = Class.forName("android.app.ActivityThread") @@ -179,5 +241,7 @@ internal class AppDataCollector( * good approximation for how long the app has been running. */ fun getDurationMs(): Long = SystemClock.elapsedRealtime() - startTimeMs + + private const val IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170 } } diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index 4597fca69f..09e64143ef 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -2,6 +2,7 @@ + CyclomaticComplexMethod:ExitInfoCallback.kt$ExitInfoCallback$@SuppressLint("SwitchIntDef") @Suppress("DEPRECATION") private fun importanceDescriptionOf(exitInfo: ApplicationExitInfo) CyclomaticComplexMethod:ExitInfoCallback.kt$ExitInfoCallback$private fun exitReasonOf(exitInfo: ApplicationExitInfo) LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit ) MagicNumber:TraceParser.kt$TraceParser$16 diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt index 21053f6a41..0feefbf78c 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt @@ -1,6 +1,21 @@ package com.bugsnag.android +import android.annotation.SuppressLint import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING_PRE_28 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE import android.app.ApplicationExitInfo import android.content.Context import android.os.Build @@ -62,16 +77,24 @@ internal class ExitInfoCallback( else -> "unknown reason (${exitInfo.reason})" } + @SuppressLint("SwitchIntDef") + @Suppress("DEPRECATION") private fun importanceDescriptionOf(exitInfo: ApplicationExitInfo) = when (exitInfo.importance) { - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND -> "foreground" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING -> "top sleeping" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE -> "visible" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE -> "perceptible" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE -> "can't save state" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE -> "service" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED -> "cached" - ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE -> "gone" + IMPORTANCE_FOREGROUND -> "foreground" + IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" + IMPORTANCE_TOP_SLEEPING -> "top sleeping" + IMPORTANCE_TOP_SLEEPING_PRE_28 -> "top sleeping" + IMPORTANCE_VISIBLE -> "visible" + IMPORTANCE_PERCEPTIBLE -> "perceptible" + IMPORTANCE_PERCEPTIBLE_PRE_26 -> "perceptible" + IMPORTANCE_CANT_SAVE_STATE -> "can't save state" + IMPORTANCE_CANT_SAVE_STATE_PRE_26 -> "can't save state" + IMPORTANCE_SERVICE -> "service" + IMPORTANCE_CACHED -> "cached/background" + IMPORTANCE_GONE -> "gone" + IMPORTANCE_EMPTY -> "empty" + REASON_PROVIDER_IN_USE -> "provider in use" + REASON_SERVICE_IN_USE -> "service in use" else -> "unknown importance (${exitInfo.importance})" } @@ -86,6 +109,7 @@ internal class ExitInfoCallback( allExitInfo.find { it.pid == pid } internal companion object { - const val MAX_EXIT_INFO = 100 + private const val MAX_EXIT_INFO = 100 + private const val IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170 } }