diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt index 7e85fad2b9998b3a38909bb4fcbdb1030f9955e9..f8bf8c8b244976271effd64fc6855db444dfa32f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt @@ -34,6 +34,7 @@ import butterknife.BindView import butterknife.ButterKnife import com.google.gson.Gson import dagger.android.support.DaggerFragment +import de.kuschku.malheur.CrashHandler import de.kuschku.malheur.data.Report import de.kuschku.quasseldroid.BuildConfig import de.kuschku.quasseldroid.R @@ -70,6 +71,29 @@ class CrashFragment : DaggerFragment() { handlerThread.quit() } + private fun reload() { + val crashDir = this.crashDir + val gson = this.gson + val context = this.context + + if (crashDir != null && context != null) { + crashDir.mkdirs() + val list: List<Pair<Report, Uri>> = crashDir.listFiles() + .orEmpty() + .map { + Pair<Report, Uri>( + gson.fromJson(it.readText()), + FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", it) + ) + } + .sortedByDescending { it.first.environment?.crashTime } + + activity?.runOnUiThread { + this.adapter?.submitList(list) + } + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.preferences_crash, container, false) @@ -89,49 +113,33 @@ class CrashFragment : DaggerFragment() { crashesEmpty.visibleIf(it.isEmpty()) } - handler.post { - val crashDir = this.crashDir - val gson = this.gson - val context = this.context - - if (crashDir != null && context != null) { - crashDir.mkdirs() - val list: List<Pair<Report, Uri>> = crashDir.listFiles() - .orEmpty() - .map { - Pair<Report, Uri>( - gson.fromJson(it.readText()), - FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", it) - ) - } - .sortedByDescending { it.first.environment?.crashTime } - - activity?.runOnUiThread { - this.adapter?.submitList(list) - } - } - } + handler.post(this::reload) return view } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.activity_crashes, menu) + menu.findItem(R.id.action_generate_crash_report).isVisible = BuildConfig.DEBUG super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { - R.id.action_delete_all -> { + R.id.action_generate_crash_report -> { + handler.post { + CrashHandler.handleSync(Exception("User requested generation of report")) + reload() + } + } + R.id.action_delete_all -> { handler.post { crashDir?.mkdirs() crashDir?.listFiles()?.forEach { it.delete() } - activity?.runOnUiThread { - this.adapter?.submitList(emptyList()) - } + reload() } true } - else -> super.onOptionsItemSelected(item) + else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/res/menu/activity_crashes.xml b/app/src/main/res/menu/activity_crashes.xml index 727490b9c6e7cdbc7296cbc1d67fef6092b73e04..1805ac59ab396c8b5568f030d3866c0aeb2bdc7e 100644 --- a/app/src/main/res/menu/activity_crashes.xml +++ b/app/src/main/res/menu/activity_crashes.xml @@ -18,6 +18,9 @@ --> <menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/action_generate_crash_report" + android:title="@string/label_generate_crash_report" /> <item android:id="@+id/action_delete_all" android:title="@string/label_delete_all" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a45eef6b23ac30963de0ffa637fee56e9118a733..1ad475ad628c724f1026e181863c9a8e425f029e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -57,6 +57,7 @@ <string name="label_edit_topic_long">Open dialog to change the channel topic</string> <string name="label_filter_messages">Filter Messages</string> <string name="label_finish">Finish</string> + <string name="label_generate_crash_report">Generate Crash Report</string> <string name="label_hide_perm">Hide Permanently</string> <string name="label_hide_temp">Hide Temporarily</string> <string name="label_ignore">Ignore</string> diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts index 5b48600e33377dbd30873414443fa6243a20d134..ff4b6cd11f620a756b9da74eda5e3e1dcc3fc00d 100644 --- a/malheur/build.gradle.kts +++ b/malheur/build.gradle.kts @@ -47,4 +47,5 @@ dependencies { implementation(kotlin("stdlib", "1.3.30")) implementation("com.google.code.gson", "gson", "2.8.5") + implementation("androidx.annotation", "annotation", "1.0.1") } diff --git a/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt b/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt index 5f68831167d29c86eacedc0bbfd52666048b344e..c3de2421d8ab1360ec3c5f18847db350098a1f20 100644 --- a/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt +++ b/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt @@ -36,6 +36,7 @@ object CrashHandler { private val startTime = Date() private var originalHandler: Thread.UncaughtExceptionHandler? = null + private var myHandler: ((Thread, Throwable) -> Unit)? = null private lateinit var handler: Handler @@ -54,64 +55,71 @@ object CrashHandler { handler = Handler(handlerThread.looper) val reportCollector = ReportCollector(application) - myHandler = Thread.UncaughtExceptionHandler { activeThread, throwable -> + myHandler = { activeThread, throwable -> val crashTime = Date() val stackTraces = Thread.getAllStackTraces() Log.e("Malheur", "Creating crash report") handler.post { Toast.makeText(application, "Creating crash report", Toast.LENGTH_LONG).show() } - Thread { - try { - val json = gson.toJson( - reportCollector.collect( - CrashContext( - application = application, - config = config, - crashingThread = activeThread, - throwable = throwable, - startTime = startTime, - crashTime = crashTime, - buildConfig = buildConfig, - stackTraces = stackTraces - ), config - ) + try { + val json = gson.toJson( + reportCollector.collect( + CrashContext( + application = application, + config = config, + crashingThread = activeThread, + throwable = throwable, + startTime = startTime, + crashTime = crashTime, + buildConfig = buildConfig, + stackTraces = stackTraces + ), config ) - val crashDirectory = File(application.cacheDir, "crashes") - crashDirectory.mkdirs() - val crashFile = File(crashDirectory, "${System.currentTimeMillis()}.json") - crashFile.createNewFile() - crashFile.writeText(json) - Log.e("Malheur", "Crash report saved: $crashFile", throwable) - handler.post { - Toast.makeText( - application, "Crash report saved: ${crashFile.name}", Toast.LENGTH_LONG - ).show() - } - } catch (e: Throwable) { - e.printStackTrace() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - throwable.addSuppressed(e) - } + ) + val crashDirectory = File(application.cacheDir, "crashes") + crashDirectory.mkdirs() + val crashFile = File(crashDirectory, "${System.currentTimeMillis()}.json") + crashFile.createNewFile() + crashFile.writeText(json) + Log.e("Malheur", "Crash report saved: $crashFile", throwable) + handler.post { + Toast.makeText( + application, "Crash report saved: ${crashFile.name}", Toast.LENGTH_LONG + ).show() } - }.start() + } catch (e: Throwable) { + e.printStackTrace() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + throwable.addSuppressed(e) + } + } } Thread.setDefaultUncaughtExceptionHandler { currentThread, throwable -> - myHandler?.uncaughtException(currentThread, throwable) - originalHandler?.uncaughtException(currentThread, throwable) + Thread { + myHandler?.invoke(currentThread, throwable) + originalHandler?.uncaughtException(currentThread, throwable) + }.start() } val oldHandler = Thread.currentThread().uncaughtExceptionHandler Thread.currentThread().setUncaughtExceptionHandler { currentThread, throwable -> - myHandler?.uncaughtException(currentThread, throwable) - oldHandler?.uncaughtException(currentThread, throwable) + Thread { + myHandler?.invoke(currentThread, throwable) + oldHandler?.uncaughtException(currentThread, throwable) + }.start() } } fun handle(throwable: Throwable) { - myHandler?.uncaughtException(Thread.currentThread(), throwable) + val thread = Thread.currentThread() + Thread { + myHandler?.invoke(thread, throwable) + }.start() } - private var myHandler: Thread.UncaughtExceptionHandler? = null + fun handleSync(throwable: Throwable) { + myHandler?.invoke(Thread.currentThread(), throwable) + } } diff --git a/malheur/src/main/java/de/kuschku/malheur/collectors/AppCollector.kt b/malheur/src/main/java/de/kuschku/malheur/collectors/AppCollector.kt index 42bd4cf6253ad317028e4ec18c995c9da2b4c402..26721baa1e377d4254a81d7ae57506c7465aab46 100644 --- a/malheur/src/main/java/de/kuschku/malheur/collectors/AppCollector.kt +++ b/malheur/src/main/java/de/kuschku/malheur/collectors/AppCollector.kt @@ -43,6 +43,9 @@ class AppCollector(private val application: Application) : Collector<AppInfo, Ap reflectionCollectConstants( context.buildConfig ?: getBuildConfigClass(application.packageName) ) + }, + installationSource = collectIf(config.installationSource) { + application.packageManager.getInstallerPackageName(application.packageName) } ) diff --git a/malheur/src/main/java/de/kuschku/malheur/collectors/DisplayCollector.kt b/malheur/src/main/java/de/kuschku/malheur/collectors/DisplayCollector.kt index 212e74abfb3c46dcc22f6ab73c295874591b7d2d..d17e207e144b871034c446632bc66b699bfbe6c5 100644 --- a/malheur/src/main/java/de/kuschku/malheur/collectors/DisplayCollector.kt +++ b/malheur/src/main/java/de/kuschku/malheur/collectors/DisplayCollector.kt @@ -26,6 +26,7 @@ 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 @@ -69,6 +70,7 @@ class DisplayCollector(application: Application) : ) } + @RequiresApi(Build.VERSION_CODES.N) private fun getHdrCapabilitiesEnum(): SparseArray<String> { val hdrCapabilityEnums = SparseArray<String>() Display.HdrCapabilities::class.java.declaredFields.filter { diff --git a/malheur/src/main/java/de/kuschku/malheur/config/AppConfig.kt b/malheur/src/main/java/de/kuschku/malheur/config/AppConfig.kt index 994ff8fd32c98b2211af570b9c2695140cbdfb0f..c028832e0d019acccbc42ffc2a52d9ea6bf1aa02 100644 --- a/malheur/src/main/java/de/kuschku/malheur/config/AppConfig.kt +++ b/malheur/src/main/java/de/kuschku/malheur/config/AppConfig.kt @@ -22,5 +22,6 @@ package de.kuschku.malheur.config data class AppConfig( val versionName: Boolean = true, val versionCode: Boolean = true, - val buildConfig: Boolean = true + val buildConfig: Boolean = true, + val installationSource: Boolean = true ) diff --git a/malheur/src/main/java/de/kuschku/malheur/data/AppInfo.kt b/malheur/src/main/java/de/kuschku/malheur/data/AppInfo.kt index c06791382bd98ad9fa5c60a6bec7b6144c045b53..928a0a50e18c2cdeb71790d55317e966e0dad209 100644 --- a/malheur/src/main/java/de/kuschku/malheur/data/AppInfo.kt +++ b/malheur/src/main/java/de/kuschku/malheur/data/AppInfo.kt @@ -22,5 +22,6 @@ package de.kuschku.malheur.data data class AppInfo( val versionName: String?, val versionCode: Long?, - val buildConfig: Map<String, Any?>? + val buildConfig: Map<String, Any?>?, + val installationSource: String? )