Skip to content
Snippets Groups Projects
Unverified Commit e4e3abb5 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

feat: massively simplify crash reporter

parent ca17307d
Branches
Tags
No related merge requests found
Pipeline #3019 failed
Showing
with 33 additions and 513 deletions
...@@ -90,7 +90,7 @@ class CrashAdapter : ListAdapter<Pair<Report?, Uri>, CrashAdapter.CrashViewHolde ...@@ -90,7 +90,7 @@ class CrashAdapter : ListAdapter<Pair<Report?, Uri>, CrashAdapter.CrashViewHolde
this.item = item this.item = item
this.uri = uri this.uri = uri
binding.crashTime.text = item?.environment?.crashTime?.let { binding.crashTime.text = item?.timestamp?.let {
dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault())) dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault()))
} ?: "null" } ?: "null"
binding.versionName.text = item?.application?.versionName ?: "null" binding.versionName.text = item?.application?.versionName ?: "null"
......
...@@ -75,7 +75,7 @@ class CrashFragment : DaggerFragment() { ...@@ -75,7 +75,7 @@ class CrashFragment : DaggerFragment() {
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", it) FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", it)
) )
} }
.sortedByDescending { it.first?.environment?.crashTime } .sortedByDescending { it.first?.timestamp }
activity?.runOnUiThread { activity?.runOnUiThread {
this.adapter?.submitList(list) this.adapter?.submitList(list)
......
...@@ -24,7 +24,6 @@ import android.os.Build ...@@ -24,7 +24,6 @@ import android.os.Build
import de.kuschku.malheur.CrashContext import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.AppConfig import de.kuschku.malheur.config.AppConfig
import de.kuschku.malheur.data.AppInfo import de.kuschku.malheur.data.AppInfo
import de.kuschku.malheur.util.reflectionCollectConstants
class AppCollector(private val application: Application) : Collector<AppInfo, AppConfig> { class AppCollector(private val application: Application) : Collector<AppInfo, AppConfig> {
override fun collect(context: CrashContext, config: AppConfig) = AppInfo( override fun collect(context: CrashContext, config: AppConfig) = AppInfo(
...@@ -39,20 +38,13 @@ class AppCollector(private val application: Application) : Collector<AppInfo, Ap ...@@ -39,20 +38,13 @@ class AppCollector(private val application: Application) : Collector<AppInfo, Ap
application.packageManager.getPackageInfo(application.packageName, 0).versionCode.toLong() application.packageManager.getPackageInfo(application.packageName, 0).versionCode.toLong()
} }
}, },
buildConfig = collectIf(config.buildConfig) {
reflectionCollectConstants(
context.buildConfig ?: getBuildConfigClass(application.packageName)
)
},
installationSource = collectIf(config.installationSource) { installationSource = collectIf(config.installationSource) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
application.packageManager.getInstallSourceInfo(application.packageName).originatingPackageName
} else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
application.packageManager.getInstallerPackageName(application.packageName) application.packageManager.getInstallerPackageName(application.packageName)
} }
)
private fun getBuildConfigClass(packageName: String) = try {
Class.forName("$packageName.BuildConfig")
} catch (e: ClassNotFoundException) {
null
} }
)
} }
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.collectors
import android.annotation.SuppressLint
import android.app.Application
import android.content.res.Configuration
import android.util.SparseArray
import de.kuschku.malheur.CrashContext
import java.lang.reflect.Field
import java.lang.reflect.Modifier
class ConfigurationCollector(private val application: Application) :
Collector<Map<String, Any?>, Boolean> {
private val configValueInfo = mutableMapOf<String, SparseArray<String>>()
private val configurationFields = listOf(
FieldDefinition(
fieldName = "screenHeightDp"
),
FieldDefinition(
fieldName = "screenWidthDp"
),
FieldDefinition(
fieldName = "smallestScreenWidthDp"
),
FieldDefinition(
fieldName = "navigation",
enumPrefix = "NAVIGATION"
),
FieldDefinition(
fieldName = "navigationHidden",
enumPrefix = "NAVIGATIONHIDDEN"
),
FieldDefinition(
fieldName = "orientation",
enumPrefix = "ORIENTATION"
),
FieldDefinition(
fieldName = "screenLayout",
enumPrefix = "SCREENLAYOUT",
isFlag = true
),
FieldDefinition(
fieldName = "touchscreen",
enumPrefix = "TOUCHSCREEN"
),
FieldDefinition(
fieldName = "uiMode",
enumPrefix = "UI_MODE",
isFlag = true
)
)
init {
val configurationFieldPrefixes = configurationFields.map(FieldDefinition::enumPrefix)
Configuration::class.java.declaredFields.filter {
Modifier.isStatic(it.modifiers)
}.filter {
it.type == Int::class.java
}.filterNot {
it.name.endsWith("_MASK")
}.forEach { field ->
val group = configurationFieldPrefixes.find { field.name.startsWith(it + "_") }
if (group != null) {
val value = field.name.substring(group.length + 1)
configValueInfo.getOrPut(group, ::SparseArray).put(field.getInt(null), value)
}
}
}
@SuppressLint("PrivateApi")
override fun collect(context: CrashContext,
config: Boolean) = configurationFields.mapNotNull { info ->
val field: Field? = Configuration::class.java.getDeclaredField(info.fieldName)
field?.let {
Pair(info, it)
}
}.filter { (_, field) ->
!Modifier.isStatic(field.modifiers)
}.map { (info, field) ->
val groupInfo = configValueInfo[info.enumPrefix]
if (groupInfo != null) {
val value = field.getInt(application.resources.configuration)
if (info.isFlag) {
info.fieldName to (0 until groupInfo.size()).map { idx ->
groupInfo.keyAt(idx) to groupInfo.valueAt(idx)
}.filter { (key, _) ->
value and key != 0
}.map { (_, value) ->
value
}.toList()
} else {
val valueConstant = groupInfo[value]
if (valueConstant == null) {
info.fieldName to value
} else {
info.fieldName to valueConstant
}
}
} else {
val value = field.getInt(application.resources.configuration)
info.fieldName to value
}
}.toMap()
class FieldDefinition(
val fieldName: String,
val enumPrefix: String? = null,
val isFlag: Boolean = false
)
}
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.collectors
import android.app.Application
import android.os.Build
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.DeviceConfig
import de.kuschku.malheur.data.DeviceInfo
import de.kuschku.malheur.util.reflectionCollectConstants
import java.io.File
class DeviceCollector(private val application: Application) : Collector<DeviceInfo, DeviceConfig> {
private val displayCollector = DisplayCollector(application)
override fun collect(context: CrashContext, config: DeviceConfig): DeviceInfo {
return DeviceInfo(
build = collectIf(config.build) {
reflectionCollectConstants(Build::class.java)
},
version = collectIf(config.version) {
reflectionCollectConstants(Build.VERSION::class.java)
},
processor = collectIf(config.processor) {
readProcInfo()
},
display = displayCollector.collectIf(context, config.display)
)
}
private fun readProcInfo() = File("/proc/cpuinfo")
.bufferedReader(Charsets.UTF_8)
.lineSequence()
.map { line -> line.split(":") }
.filter { split -> split.size == 2 }
.map { (key, value) -> key.trim() to value.trim() }
.filter { (key, _) -> key == "Hardware" }
.map { (_, value) -> value }
.firstOrNull()
}
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.collectors
import android.app.Application
import android.content.Context
import android.graphics.Point
import android.os.Build
import android.util.SparseArray
import android.view.Display
import android.view.WindowManager
import androidx.annotation.RequiresApi
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.data.DisplayInfo
import de.kuschku.malheur.data.MetricsInfo
import de.kuschku.malheur.util.getMetrics
import java.lang.reflect.Modifier
class DisplayCollector(application: Application) :
Collector<DisplayInfo, Boolean> {
private val windowManager = application.getSystemService(
Context.WINDOW_SERVICE
) as WindowManager
@Suppress("DEPRECATION")
override fun collect(context: CrashContext, config: Boolean): DisplayInfo {
val display = windowManager.defaultDisplay
val hdrCapabilities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val capabilitiesEnum = getHdrCapabilitiesEnum()
display.hdrCapabilities.supportedHdrTypes.map(capabilitiesEnum::get)
} else {
null
}
val size = Point().also {
display.getRealSize(it)
}
return DisplayInfo(
width = size.x,
height = size.y,
refreshRate = display.refreshRate,
hdr = hdrCapabilities,
isWideGamut = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
display.isWideColorGamut
} else {
null
},
metrics = MetricsInfo(display.getMetrics())
)
}
@RequiresApi(Build.VERSION_CODES.N)
private fun getHdrCapabilitiesEnum(): SparseArray<String> {
val hdrCapabilityEnums = SparseArray<String>()
Display.HdrCapabilities::class.java.declaredFields.filter {
Modifier.isStatic(it.modifiers)
}.filter {
it.name.startsWith("HDR_TYPE_")
}.filter {
it.type == Int::class.java
}.forEach {
try {
val value = it.getInt(null)
hdrCapabilityEnums.put(value, it.name.substring("HDR_TYPE_".length))
} catch (e: IllegalAccessException) {
}
}
return hdrCapabilityEnums
}
}
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.collectors
import android.app.Application
import android.os.Debug
import android.os.Environment
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.EnvConfig
import de.kuschku.malheur.data.EnvInfo
import de.kuschku.malheur.data.MemoryInfo
import de.kuschku.malheur.util.reflectionCollectGetters
import java.io.File
class EnvCollector(application: Application) : Collector<EnvInfo, EnvConfig> {
private val configurationCollector = ConfigurationCollector(application)
override fun collect(context: CrashContext, config: EnvConfig) = EnvInfo(
paths = collectIf(config.paths) {
reflectionCollectGetters(
Environment::class.java
)?.map { (key, value) ->
key to if (value is File) {
value.canonicalPath
} else {
value
}
}?.toMap()
},
memory = collectIf(config.memory) {
val memoryInfo = Debug.MemoryInfo()
Debug.getMemoryInfo(memoryInfo)
MemoryInfo(memoryInfo)
},
configuration = configurationCollector.collectIf(context, config.configuration),
startTime = collectIf(config.startTime) {
context.startTime.time
},
crashTime = collectIf(config.crashTime) {
context.crashTime.time
}
)
}
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.collectors
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.LogConfig
import java.text.SimpleDateFormat
import java.util.*
class LogCollector : Collector<Map<String, List<String>>, LogConfig> {
private val logcatTimeFormatter = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.ROOT)
override fun collect(context: CrashContext, config: LogConfig): Map<String, List<String>> {
val since = logcatTimeFormatter.format(context.startTime)
return config.buffers.map { buffer ->
buffer to readLogCat(since, buffer)
}.toMap()
}
private fun readLogCat(since: String, buffer: String) = ProcessBuilder()
.command("logcat", "-t", since, "-b", buffer)
.redirectErrorStream(true)
.start()
.inputStream
.bufferedReader(Charsets.UTF_8)
.readLines()
}
...@@ -27,17 +27,12 @@ import de.kuschku.malheur.data.Report ...@@ -27,17 +27,12 @@ import de.kuschku.malheur.data.Report
class ReportCollector(application: Application) : Collector<Report, ReportConfig> { class ReportCollector(application: Application) : Collector<Report, ReportConfig> {
private val crashCollector = CrashCollector() private val crashCollector = CrashCollector()
private val threadCollector = ThreadCollector() private val threadCollector = ThreadCollector()
private val logcatCollector = LogCollector()
private val applicationCollector = AppCollector(application) private val applicationCollector = AppCollector(application)
private val deviceCollector = DeviceCollector(application)
private val environmentCollector = EnvCollector(application)
override fun collect(context: CrashContext, config: ReportConfig) = Report( override fun collect(context: CrashContext, config: ReportConfig) = Report(
timestamp = System.currentTimeMillis(),
crash = crashCollector.collectIf(context, config.crash), crash = crashCollector.collectIf(context, config.crash),
threads = threadCollector.collectIf(context, config.threads), threads = threadCollector.collectIf(context, config.threads),
/*logcat = logcatCollector.collectIf(context, config.logcat),*/
application = applicationCollector.collectIf(context, config.application), application = applicationCollector.collectIf(context, config.application),
device = deviceCollector.collectIf(context, config.device),
environment = environmentCollector.collectIf(context, config.environment)
) )
} }
...@@ -19,9 +19,11 @@ ...@@ -19,9 +19,11 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class AppInfo( data class AppInfo(
val versionName: String?, val versionName: String?,
val versionCode: Long?, val versionCode: Long?,
val buildConfig: Map<String, Any?>?,
val installationSource: String? val installationSource: String?
) )
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class CrashInfo( data class CrashInfo(
val cause: ExceptionInfo?, val cause: ExceptionInfo?,
val exception: String? val exception: String?
......
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.data
data class DeviceInfo(
val build: Map<String, Any?>?,
val version: Map<String, Any?>?,
val processor: String?,
val display: DisplayInfo?
)
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class DisplayInfo( data class DisplayInfo(
val width: Int, val width: Int,
val height: Int, val height: Int,
val refreshRate: Float,
val hdr: List<String>?,
val isWideGamut: Boolean?,
val metrics: MetricsInfo val metrics: MetricsInfo
) )
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.data
data class EnvInfo(
val paths: Map<String, Any?>?,
val memory: MemoryInfo?,
val configuration: Map<String, Any?>?,
val startTime: Long?,
val crashTime: Long?
)
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class ExceptionInfo( data class ExceptionInfo(
val type: String?, val type: String?,
val message: String?, val message: String?,
......
/*
* Quasseldroid - Quassel client for Android
*
* Copyright (c) 2020 Janne Mareike Koschinski
* Copyright (c) 2020 The Quassel Project
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.kuschku.malheur.data
import android.os.Debug
data class MemoryInfo(
var dalvikPss: Int?,
var dalvikPrivateDirty: Int?,
var dalvikSharedDirty: Int?,
var nativePss: Int?,
var nativePrivateDirty: Int?,
var nativeSharedDirty: Int?,
var otherPss: Int?,
var otherPrivateDirty: Int?,
var otherSharedDirty: Int?
) {
constructor(memoryInfo: Debug.MemoryInfo?) : this(
dalvikPss = memoryInfo?.dalvikPss,
dalvikPrivateDirty = memoryInfo?.dalvikPrivateDirty,
dalvikSharedDirty = memoryInfo?.dalvikSharedDirty,
nativePss = memoryInfo?.nativePss,
nativePrivateDirty = memoryInfo?.nativePrivateDirty,
nativeSharedDirty = memoryInfo?.nativeSharedDirty,
otherPss = memoryInfo?.otherPss,
otherPrivateDirty = memoryInfo?.otherPrivateDirty,
otherSharedDirty = memoryInfo?.otherSharedDirty
)
}
...@@ -20,7 +20,9 @@ ...@@ -20,7 +20,9 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import android.util.DisplayMetrics import android.util.DisplayMetrics
import kotlinx.serialization.Serializable
@Serializable
data class MetricsInfo( data class MetricsInfo(
val density: Float, val density: Float,
val scaledDensity: Float, val scaledDensity: Float,
......
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class Report( data class Report(
val timestamp: Long? = null,
val crash: CrashInfo? = null, val crash: CrashInfo? = null,
val threads: ThreadsInfo? = null, val threads: ThreadsInfo? = null,
val logcat: Map<String, List<String>?>? = null,
val application: AppInfo? = null, val application: AppInfo? = null,
val device: DeviceInfo? = null,
val environment: EnvInfo? = null
) )
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class ThreadInfo( data class ThreadInfo(
val id: Long?, val id: Long?,
val name: String?, val name: String?,
......
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
package de.kuschku.malheur.data package de.kuschku.malheur.data
import kotlinx.serialization.Serializable
@Serializable
data class ThreadsInfo( data class ThreadsInfo(
val crashed: ThreadInfo?, val crashed: ThreadInfo?,
val others: List<ThreadInfo>? val others: List<ThreadInfo>?
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment