Compare commits

...

6 Commits

Author SHA1 Message Date
ae8f26247b
feat: Add jitpack Config (#13)
Some checks failed
Android CI / build (push) Failing after 11m15s
2023-08-24 16:20:47 +02:00
acbb524a7e
feat: Add crash saving (#12) 2023-08-24 15:54:55 +02:00
1703479865
chore: Add links to README
Some checks failed
Android CI / build (push) Failing after 1m57s
2023-08-22 18:27:02 +02:00
06777bfef1
feat: change to use strings (#9)
Some checks failed
build
2023-03-22 14:32:17 +01:00
64d9b25883
feat: simplified sample app code to better understand how it work (#10) 2023-03-17 17:27:01 +01:00
71993d0291
misc: update README.md with a usage example (#11) 2023-03-17 17:26:13 +01:00
25 changed files with 534 additions and 232 deletions

View File

@ -1,37 +0,0 @@
#!/bin/bash
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -xe
# Default Gradle settings are not optimal for Android builds, override them
# here to make the most out of the GitHub Actions build servers
GRADLE_OPTS="$GRADLE_OPTS -Xms4g -Xmx4g"
GRADLE_OPTS="$GRADLE_OPTS -XX:+HeapDumpOnOutOfMemoryError"
GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.daemon=false"
GRADLE_OPTS="$GRADLE_OPTS -Dorg.gradle.workers.max=2"
GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.incremental=false"
GRADLE_OPTS="$GRADLE_OPTS -Dkotlin.compiler.execution.strategy=in-process"
GRADLE_OPTS="$GRADLE_OPTS -Dfile.encoding=UTF-8"
export GRADLE_OPTS
# Crawl all gradlew files which indicate an Android project
# You may edit this if your repo has a different project structure
for GRADLEW in `find . -name "gradlew"` ; do
SAMPLE=$(dirname "${GRADLEW}")
# Tell Gradle that this is a CI environment and disable parallel compilation
bash "$GRADLEW" -p "$SAMPLE" -Pci --no-parallel --stacktrace $@
done

View File

@ -27,16 +27,14 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Build project
run: |
chmod +x .github/scripts/gradlew_recursive.sh
.github/scripts/gradlew_recursive.sh assembleDebug
run: ./gradlew assembleDebug
- name: Zip artifacts
run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
- name: Upload artifacts

View File

@ -1,3 +1,19 @@
<p align="center">
<img alt="Dzeio Charts logo" width="30%" src="sample/src/main/ic_launcher-playstore.png">
</p>
<p align="center">
<a href="https://discord.gg/d3QeWKBmBD">
<img src="https://img.shields.io/discord/1143555541004726272?color=%235865F2&label=Discord" alt="Discord Link">
</a>
<a href="https://github.com/dzeiocom/crashhandler/stargazers">
<img src="https://img.shields.io/github/stars/dzeiocom/crashhandler?style=flat-square" alt="Github stars">
</a>
<a href="https://github.com/dzeiocom/crashhandler/actions/workflows/build.yml">
<img src="https://img.shields.io/github/actions/workflow/status/dzeiocom/crashhandler/build.yml?style=flat-square" alt="Build passing" />
</a>
</p>
# Crash Handler
Lightweight & customizable crash android crash handler library
@ -7,8 +23,43 @@ Lightweight & customizable crash android crash handler library
- Add Jitpack.io to your `settings.gradle` file `maven { url 'https://jitpack.io' }`
Add to you dependencies (check the latest release for the version):
- (Gradle Kotlin DSL) Add `implementation("com.dzeio:crashhandler:1.0.0")`
- (Gradle Groovy DSL) Add `implementation "com.dzeio:crashhandler:1.0.0" `
- (Gradle Kotlin DSL) Add `implementation("com.dzeio:crashhandler:1.0.2")`
- (Gradle Groovy DSL) Add `implementation "com.dzeio:crashhandler:1.0.2"`
## Usage
_note: full featured example in the `sample` app_
Create and add this to your Application.{kt,java}
```kotlin
// create the Crash Handler
CrashHandler.Builder()
// need the application context to run
.withContext(this)
// every other items below are optionnal
// define a custom activity to use
.withActivity(ErrorActivity::class.java)
// define the preferenceManager to have the previous crash date in the logs
.withPrefs(prefs)
.withPrefsKey("com.dzeio.crashhandler.key")
// a Prefix to add at the beginning the crash message
.withPrefix("Prefix")
// a Suffix to add at the end of the crash message
.withSuffix("Suffix")
// add a location where the crash logs are also exported (can be recovered as a zip ByteArray by calling {CrashHandler.getInstance().export()})
.withExportLocation(
File(this.getExternalFilesDir(null) ?: this.filesDir, "crash-logs")
)
// build & start the module
.build().setup()
```
## Build

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.4.2")
classpath("com.android.tools.build:gradle:8.1.1")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -12,8 +12,8 @@ buildscript {
}
plugins {
id("com.android.application") version "7.4.2" apply false
id("com.android.library") version "7.4.2" apply false
id("com.android.application") version "8.1.1" apply false
id("com.android.library") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.7.0" apply false
}

View File

@ -4,15 +4,39 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
#Tue Jul 19 18:42:00 CEST 2022
# enable non transitive R Classes
android.nonTransitiveRClass=true
# use official kotlin style
kotlin.code.style=official
# use androidX
android.useAndroidX=true
# Disable Jetifier
android.enableJetifier=false
org.gradle.unsafe.configuration-cache=true
# Disable configuration cache
org.gradle.unsafe.configuration-cache=false
# Enable Gradle Daemon
org.gradle.daemon=true
# Enable Configure on demand
org.gradle.configureondemand=true
# Enable gradle caching
org.gradle.caching=true
# Enabled Build config
android.defaults.buildfeatures.buildconfig=true
# BREAK EVERYTHING
android.nonFinalResIds=false

View File

@ -1,6 +1,6 @@
#Sun Aug 07 22:38:24 CEST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

0
gradlew vendored Normal file → Executable file
View File

0
gradlew.bat vendored Normal file → Executable file
View File

5
jitpack.yml Normal file
View File

@ -0,0 +1,5 @@
before_install:
- export SDKMAN_DIR="/home/jitpack/.sdkman/"
- source "/home/jitpack/.sdkman/bin/sdkman-init.sh"
- sdk install java 17.0.1-open
- sdk use java 17.0.1-open

View File

@ -6,7 +6,7 @@ plugins {
val artifact = "crashhandler"
group = "com.dzeio"
val projectVersion = project.findProperty("version") as String? ?: "1.0.0"
val projectVersion = project.findProperty("version") as String? ?: "1.1.0"
version = projectVersion
publishing {
@ -37,6 +37,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
buildConfigField("String", "VERSION", "\"$projectVersion\"")
}
testFixtures {

View File

@ -1,5 +1,6 @@
package com.dzeio.crashhandler
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.Intent
@ -9,10 +10,16 @@ import android.os.Process
import android.util.Log
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.core.content.edit
import com.dzeio.crashhandler.CrashHandler.Builder
import com.dzeio.crashhandler.ui.ErrorActivity
import com.dzeio.crashhandler.utils.ZipFile
import java.io.File
import java.io.IOException
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.system.exitProcess
import java.util.TimeZone
/**
* the Crash Handler class, you can get an instance by using it's [Builder]
@ -25,11 +32,24 @@ class CrashHandler private constructor(
@StringRes
private val errorReporterCrashKey: Int?,
private val prefix: String? = null,
private val suffix: String? = null
private val suffix: String? = null,
private val exportFolder: File? = null
) {
private companion object {
companion object {
private const val TAG = "CrashHandler"
private var instance: CrashHandler? = null
/**
* get the instance of the CrashHandler it will crash if it was not initialized previously
*/
fun getInstance(): CrashHandler {
if (this.instance == null) {
throw Exception("can't get CrashHandler instance as its not initialized")
}
return this.instance!!
}
}
/**
@ -43,6 +63,7 @@ class CrashHandler private constructor(
private var activity: Class<*>? = ErrorActivity::class.java
private var prefix: String? = null
private var suffix: String? = null
private var exportLocation: File? = null
/**
* Change the Crash activity to with your own
@ -124,6 +145,16 @@ class CrashHandler private constructor(
return this
}
/**
* Add a crash log export folder
*
* @param exportLocation the folder in which you want to export crash logs, it will be created if it does not exists
*/
fun withExportLocation(exportLocation: File): Builder {
this.exportLocation = exportLocation
return this
}
/**
* build the Crash Handler
*/
@ -135,11 +166,16 @@ class CrashHandler private constructor(
prefsKey,
errorReporterCrashKey,
prefix,
suffix
suffix,
exportLocation
)
}
}
init {
instance = this
}
private var oldHandler: Thread.UncaughtExceptionHandler? = null
fun setup() {
@ -179,35 +215,15 @@ class CrashHandler private constructor(
// get current time an date
val now = Date().time
// prepare to build debug string
var data = "Crash report:\n\n"
data += prefix ?: ""
// add device informations
val deviceToReport =
if (Build.DEVICE.contains(Build.MANUFACTURER)) {
Build.DEVICE
} else {
"${Build.MANUFACTURER} ${Build.DEVICE}"
}
data += "\n\non $deviceToReport (${Build.MODEL}) running Android ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})"
// add the current time to it
data += "\n\nCrash happened at ${Date(now)}"
var previousCrash: Long? = null
// if lib as access to the preferences store
if (prefs != null && prefsKey != null) {
// get the last Crash
val lastCrash = prefs.getLong(prefsKey, 0L)
// then add it to the logs :D
data += "\nLast crash happened at ${Date(lastCrash)}"
previousCrash = prefs.getLong(prefsKey, 0L)
// if a crash already happened just before it means the Error Activity crashed lul
if (lastCrash >= now - 1000) {
if (previousCrash >= now - 1000) {
// log it :D
Log.e(
TAG,
@ -217,7 +233,7 @@ class CrashHandler private constructor(
// try to send a toast indicating it
Toast.makeText(
application,
errorReporterCrashKey ?: R.string.error_reporter_crash,
errorReporterCrashKey ?: R.string.crash_handler_reporter_crash,
Toast.LENGTH_LONG
).show()
@ -227,18 +243,23 @@ class CrashHandler private constructor(
}
// update the store
prefs.edit().putLong(prefsKey, now).commit()
prefs.edit(true) { putLong(prefsKey, now) }
}
Log.i(TAG, "Collecting Error")
// get Thread name and ID
data += "\n\nHappened on Thread \"${paramThread.name}\" (${paramThread.id})"
val data = this.buildData(
now,
previousCrash,
paramThread,
paramThrowable
)
// print exception backtrace
data += "\n\nException:\n${paramThrowable.stackTraceToString()}\n\n"
data += suffix ?: ""
try {
exportData(data, now)
} catch (e: IOException) {
Log.e(TAG, "Could not export the data to file", e)
}
Log.i(TAG, "Starting ${activity.name}")
@ -262,7 +283,127 @@ class CrashHandler private constructor(
// Kill self
Process.killProcess(Process.myPid())
exitProcess(10)
}
}
fun export(): ByteArray? {
if (exportFolder == null) {
return null
}
val output = ZipFile()
val files = exportFolder.listFiles()
for (file in files!!) {
output.addFile(file.name, file)
}
return output.toByteArray()
}
fun clearExports() {
if (exportFolder == null) {
return
}
val files = exportFolder.listFiles()
for (file in files!!) {
file.delete()
}
}
private fun exportData(data: String, now: Long) {
if (exportFolder == null) {
return
}
@SuppressLint("SimpleDateFormat")
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss.SSS")
sdf.timeZone = TimeZone.getTimeZone("CET")
val filename = sdf.format(Date(now))
if (!exportFolder.exists()) {
exportFolder.mkdirs()
}
if (!exportFolder.isDirectory) {
Log.e(
"CrashHandler",
"Cannot export the crash logs to a file due to the folder not being a folder"
)
return
}
val out = File(exportFolder, "$filename.log")
out.writeText(data, Charsets.UTF_8)
Log.d("CrashHandler", "Saving file to ${out.absolutePath}")
}
/**
* build the data text
* @param now the date as of right now
* @param previousCrash the previous crash date
* @param thread the thread that crashed
* @param throwable the exception thrown
*
* @return the string that contains a nicely formatted list of informations about the device
*/
private fun buildData(
now: Long,
previousCrash: Long?,
thread: Thread,
throwable: Throwable
): String {
val app = application
if (app == null) {
return "Could not build data because the library is missing the context"
}
// prepare to build debug string
var data = "${app.getString(R.string.crash_handler_crash_report)}\n\n"
// add the user submitted prefix
data += prefix ?: ""
// add device informations
val deviceToReport =
if (Build.DEVICE.contains(Build.MANUFACTURER)) {
Build.DEVICE
} else {
"${Build.MANUFACTURER} ${Build.DEVICE}"
}
// add the device informations
data += "\n\n${app.getString(
R.string.crash_handler_hard_soft_infos,
deviceToReport,
Build.MODEL,
Build.VERSION.RELEASE,
Build.VERSION.SDK_INT
)}"
// add the current time to it
data += "\n\n${app.getString(
R.string.crash_handler_crash_happened,
Date(now).toString()
)}"
// add the previous crash date if available
if (previousCrash != null) {
data += "\n${app.getString(
R.string.crash_handler_previous_crash,
Date(previousCrash).toString()
)}"
}
// get Thread name and ID
data += "\n\n${app.getString(
R.string.crash_handler_thread_infos,
thread.name,
thread.id
)}"
// print exception backtrace
data += "\n\n${app.getString(R.string.crash_handler_error)}\n${throwable.stackTraceToString()}\n\n"
data += "Generated by Dzeio Crash Handler Version ${BuildConfig.VERSION}\n\n"
// add the user submitted suffix
data += suffix ?: ""
return data
}
}

View File

@ -0,0 +1,71 @@
package com.dzeio.crashhandler.utils
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/**
* Simple Wrapper around the Java zip implementation to make it easier to use
*/
class ZipFile {
private val stream = ByteArrayOutputStream()
private val output = ZipOutputStream(BufferedOutputStream(stream))
/**
* add a file to the zip with the [content] to the specified [path]
*
* @param path the path in the Zip
* @param content the content as a String
*/
fun addFile(path: String, content: String) = addFile(path, content.toByteArray())
/**
* add the [file] to the zip with at the specified [path]
*
* @param path the path in the Zip
* @param file the file to add to the zip
*/
fun addFile(path: String, file: File) {
// Read file
val data = file.inputStream()
val bytes = data.readBytes()
data.close()
return addFile(path, bytes)
}
/**
* add the [content] to the zip with at the specified [path]
*
* @param path the path in the Zip
* @param content the content of the file to add to the zip
*/
fun addFile(path: String, content: ByteArray) {
val entry = ZipEntry(path)
try {
output.putNextEntry(entry)
output.write(content)
} catch (e: IOException) {
e.printStackTrace()
}
output.closeEntry()
}
/**
* Export the Zip file to a ByteArray
*
* **note: You can't write to the ZipFile after running this function**
*
* @return the Zip File as a [ByteArray]
*/
fun toByteArray(): ByteArray {
output.close()
return stream.toByteArray()
}
}

View File

@ -11,11 +11,12 @@
android:id="@+id/title"
style="?textAppearanceHeadline5"
android:layout_width="0dp"
android:textAlignment="center"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/error_app_crash"
android:text="@string/crash_handler_error_app_crash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -27,7 +28,8 @@
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="@string/error_app_report"
android:textAlignment="center"
android:text="@string/crash_handler_error_app_report"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
@ -48,7 +50,8 @@
android:id="@+id/error_text"
style="?textAppearanceCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:textIsSelectable="true" />
</ScrollView>
@ -59,7 +62,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:text="@string/copy_to_clipboard"
android:text="@string/crash_handler_copy_to_clipboard"
app:layout_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="@+id/error_quit"
app:layout_constraintStart_toStartOf="@+id/error_quit" />
@ -69,7 +72,7 @@
android:id="@+id/error_quit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/quit"
android:text="@string/crash_handler_quit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

View File

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error Activity Translations -->
<string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string>
<string name="error_app_report">Merci d\'envoyer le rapport d\'erreur afin que nous puissions améliorer votre experience</string>
<string name="copy_to_clipboard">Copier dans le presse papier</string>
<string name="quit">Quitter</string>
<string name="error_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</string>
<string name="crash_handler_error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string>
<string name="crash_handler_error_app_report">Merci d\'envoyer le rapport d\'erreur afin que nous puissions améliorer votre experience</string>
<string name="crash_handler_copy_to_clipboard">Copier dans le presse papier</string>
<string name="crash_handler_quit">Quitter</string>
<string name="crash_handler_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</string>
<string name="crash_handler_hard_soft_infos">sur %1$s (%2$s) avec Android %3$s (%4$d)</string>
<string name="crash_handler_crash_happened">Plantage arrivé à %1$s</string>
<string name="crash_handler_previous_crash">Plantage précédent arrivé à %1$s</string>
<string name="crash_handler_thread_infos">Arrivé sur le thread "%1$s" (%2$d)</string>
<string name="crash_handler_error">Erreur :</string>
<string name="crash_handler_crash_report">Rapport d\'erreur :</string>
</resources>

View File

@ -2,10 +2,16 @@
<resources>
<!-- Error Activity Translations -->
<string name="error_app_crash">An Error Occurred in the application forcing it to close down</string>
<string name="error_app_report">Please report it so we can enhance you\'re Android OpenHealth experience</string>
<string name="copy_to_clipboard">Copy to clipboard</string>
<string name="quit">Quit</string>
<string name="error_reporter_crash">An error occurred while making the error report</string>
<string name="crash_handler_error_app_crash">An Error Occurred in the application forcing it to close down</string>
<string name="crash_handler_error_app_report">Please report it so we can enhance you\'re Android OpenHealth experience</string>
<string name="crash_handler_copy_to_clipboard">Copy to clipboard</string>
<string name="crash_handler_quit">Quit</string>
<string name="crash_handler_reporter_crash">An error occurred while making the error report</string>
<string name="crash_handler_hard_soft_infos">on%1$s (%2$s) running Android %3$s (%4$d)</string>
<string name="crash_handler_crash_happened">Crash happened at %1$s</string>
<string name="crash_handler_previous_crash">Previous crash happened at %1$s</string>
<string name="crash_handler_thread_infos">Happened on Thread "%1$s" (%2$d)</string>
<string name="crash_handler_error">Exception:</string>
<string name="crash_handler_crash_report">Crash Report:</string>
</resources>

View File

@ -3,20 +3,34 @@ package com.dzeio.crashhandlertest
import androidx.preference.PreferenceManager
import com.dzeio.crashhandler.CrashHandler
import com.dzeio.crashhandlertest.ui.ErrorActivity
import java.io.File
class Application : android.app.Application() {
override fun onCreate() {
super.onCreate()
// get the device Preference store
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
// create the Crash Handler
CrashHandler.Builder()
.withActivity(ErrorActivity::class.java)
// need the application context to run
.withContext(this)
// every other items below are optionnal
// define a custom activity to use
.withActivity(ErrorActivity::class.java)
// define the preferenceManager to have the previous crash date in the logs
.withPrefs(prefs)
.withPrefsKey("com.dzeio.crashhandler.key")
.withPrefix("Pouet :D")
.withSuffix("WHYYYYY")
// a Prefix to add at the beginning the crash message
.withPrefix("Prefix")
// a Suffix to add at the end of the crash message
.withSuffix("Suffix")
// add a location where the crash logs are also exported (can be recovered as a zip ByteArray by calling {CrashHandler.getInstance().export()})
.withExportLocation(
File(this.getExternalFilesDir(null) ?: this.filesDir, "crash-logs")
)
// build & start the module
.build().setup()
}
}

View File

@ -3,46 +3,39 @@ package com.dzeio.crashhandlertest.ui
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.dzeio.crashhandlertest.R
import com.dzeio.crashhandlertest.databinding.ActivityErrorBinding
import kotlin.system.exitProcess
/**
* Example Activity for a custom ErrorActivity
*
* note: try to keep the complexity of this class as low as possible
* to make sure this will always load
*/
class ErrorActivity : AppCompatActivity() {
// the view binding
private lateinit var binding: ActivityErrorBinding
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
// inflate the view
binding = ActivityErrorBinding.inflate(layoutInflater)
setContentView(binding.root)
// get the error string from the library
val data = intent.getStringExtra("error")
// Get Application datas
val deviceToReport =
if (Build.DEVICE.contains(Build.MANUFACTURER)) {
Build.DEVICE
} else {
"${Build.MANUFACTURER} ${Build.DEVICE}"
}
val reportText = """
Crash Report (Thread: ${intent?.getLongExtra("threadId", -1) ?: "unknown"})
on $deviceToReport (${Build.MODEL}) running Android ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})
backtrace:
""".trimIndent() + data
// put it in the textView
binding.errorText.text = reportText
binding.errorText.text = data
// Handle the Quit button
binding.errorQuit.setOnClickListener {
@ -56,21 +49,36 @@ class ErrorActivity : AppCompatActivity() {
val intent = Intent(Intent.ACTION_SEND)
intent.setDataAndType(Uri.parse("mailto:"), "text/plain")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("report.openhealth@dzeio.com"))
intent.putExtra(Intent.EXTRA_SUBJECT, "Error report for application crash")
intent.putExtra(Intent.EXTRA_TEXT, "Send Report Email\n$reportText")
intent.putExtra(
Intent.EXTRA_SUBJECT,
getString(R.string.error_report_application_crash)
)
intent.putExtra(Intent.EXTRA_TEXT, "${getString(R.string.send_email_report)}\n$data")
// send intent
try {
startActivity(Intent.createChooser(intent, "Send Report Email..."))
startActivity(
Intent.createChooser(
intent,
getString(R.string.send_email_report)
)
)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "Not Email client found :(", Toast.LENGTH_LONG).show()
Toast.makeText(
this,
getString(R.string.no_email_client_found),
Toast.LENGTH_LONG
).show()
}
}
// Handle the GitHub Button
binding.errorSubmitGithub.setOnClickListener {
// Build URL
val url = "https://github.com/dzeiocom/OpenHealth/issues/new?title=Application Error&body=$reportText"
val title = "Application Error"
val url = "https://github.com/dzeiocom/OpenHealth/issues/new?title=$title&body=$data"
// send intent
try {
startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(url))

View File

@ -1,12 +1,41 @@
package com.dzeio.crashhandlertest.ui
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.dzeio.crashhandler.CrashHandler
import com.dzeio.crashhandlertest.R
import com.dzeio.crashhandlertest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
/**
* Response to the export button event
*/
private val writeResult = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) {
val zipFile = CrashHandler.getInstance().export()
if (zipFile == null) {
return@registerForActivityResult
}
// write file to location
this.contentResolver.openOutputStream(it!!)?.apply {
write(zipFile)
close()
}
Toast.makeText(
this,
R.string.export_complete,
Toast.LENGTH_LONG
).show()
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
@ -15,5 +44,20 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.buttonFirst.setOnClickListener {
// DIE
throw Exception(getString(R.string.error_message))
}
binding.buttonExport.setOnClickListener {
// launch the popin to select where to save the file
writeResult.launch("output.zip")
}
binding.buttonClearExports.setOnClickListener {
// clear the handler exports
CrashHandler.getInstance().clearExports()
}
}
}
}

View File

@ -1,43 +0,0 @@
package com.dzeio.crashhandlertest.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.dzeio.crashhandlertest.databinding.FragmentMainBinding
/**
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonFirst.setOnClickListener {
// DIE
throw Exception("POKÉMON :D")
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
@ -12,7 +11,8 @@
style="?textAppearanceHeadline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="blablabla"
android:textAlignment="center"
android:text="@string/crash_handler_page_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -22,7 +22,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="blablabla2"
android:textAlignment="center"
android:text="@string/crash_handler_page_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
@ -42,7 +43,8 @@
android:id="@+id/error_text"
android:layout_width="match_parent"
style="?textAppearanceCaption"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:textIsSelectable="true" />
</ScrollView>
@ -52,7 +54,7 @@
android:id="@+id/error_submit_github"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Github"
android:text="@string/github"
android:layout_marginStart="16dp"
app:layout_constraintBaseline_toBaselineOf="@+id/error_submit_email"
app:layout_constraintStart_toStartOf="parent" />
@ -63,7 +65,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="E-Mail"
android:text="@string/e_mail"
app:layout_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="parent" />
@ -72,7 +74,7 @@
android:id="@+id/error_quit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="Quit"
android:text="@string/quit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

View File

@ -1,23 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
tools:context=".ui.MainActivity"
android:padding="16dp">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_marginTop="?attr/actionBarSize"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph" />
app:layout_constraintTop_toTopOf="parent">
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<Button
android:id="@+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/crash_app"
android:layout_marginBottom="8dp" />
<Button
android:id="@+id/button_export"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/export_logs" />
<Button
android:id="@+id/button_clear_exports"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clear_export_logs" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainFragment"
android:padding="16dp">
<Button
android:id="@+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/crash_app"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/MainFragment">
<fragment
android:id="@+id/MainFragment"
android:name="com.dzeio.crashhandlertest.ui.MainFragment"
android:label="@string/first_fragment_label"
tools:layout="@layout/fragment_main"/>
</navigation>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="crash_app">Faire Planter l\'application</string>
<string name="error_message">Je suis une erreur</string>
<string name="crash_handler_page_title">Crash Handler Titre de page</string>
<string name="crash_handler_page_subtitle">Crash Handler sous-titre de page</string>
<string name="e_mail">E-Mail</string>
<string name="quit">Quitter</string>
<string name="no_email_client_found">Aucun client E-Mail trouvé!</string>
<string name="send_email_report">Envoyer le rapport de crash par E-Mail</string>
<string name="error_report_application_crash">Rapport d\'erreur pour le crash d\'application</string>
<string name="export_logs">Exporter les logs de crash sauvegardé</string>
<string name="export_complete">Logs de crash exporté!</string>
<string name="clear_export_logs">Nettoyer les crashs sauvegardé</string>
</resources>

View File

@ -1,9 +1,16 @@
<resources>
<string name="app_name" translatable="false">Crash Handler</string>
<string name="title_activity_main" translatable="false">MainActivity</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label" translatable="false">First Fragment</string>
<string name="second_fragment_label" translatable="false">Second Fragment</string>
<string name="crash_app" translatable="false">Crash app</string>
<string name="crash_app">Crash app</string>
<string name="error_message">I am an error</string>
<string name="crash_handler_page_title">Crash Handler page title</string>
<string name="crash_handler_page_subtitle">crash handler page subtitle</string>
<string name="github" translatable="false">Github</string>
<string name="e_mail">E-Mail</string>
<string name="quit">Quit</string>
<string name="no_email_client_found">No E-Mail client found!</string>
<string name="send_email_report">Send a Report E-Mail</string>
<string name="error_report_application_crash">Error report for the application crash</string>
<string name="export_logs">Export saved crash logs</string>
<string name="export_complete">Exported crash logs!</string>
<string name="clear_export_logs">Cleanup saved crash logs</string>
</resources>