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
21 changed files with 491 additions and 127 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: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: set up JDK 11 - name: set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: '11' java-version: '17'
distribution: 'adopt' distribution: 'temurin'
cache: gradle cache: gradle
- name: Build project - name: Build project
run: | run: ./gradlew assembleDebug
chmod +x .github/scripts/gradlew_recursive.sh
.github/scripts/gradlew_recursive.sh assembleDebug
- name: Zip artifacts - name: Zip artifacts
run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so' run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
- name: Upload artifacts - 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 # Crash Handler
Lightweight & customizable crash android crash handler library 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 Jitpack.io to your `settings.gradle` file `maven { url 'https://jitpack.io' }`
Add to you dependencies (check the latest release for the version): Add to you dependencies (check the latest release for the version):
- (Gradle Kotlin 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.0" ` - (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 ## Build

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -12,8 +12,8 @@ buildscript {
} }
plugins { plugins {
id("com.android.application") version "7.4.2" apply false id("com.android.application") version "8.1.1" apply false
id("com.android.library") version "7.4.2" apply false id("com.android.library") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.7.0" 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. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m # 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. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # 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 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true org.gradle.parallel=true
#Tue Jul 19 18:42:00 CEST 2022
# enable non transitive R Classes
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
# use official kotlin style
kotlin.code.style=official kotlin.code.style=official
# use androidX
android.useAndroidX=true android.useAndroidX=true
# Disable Jetifier
android.enableJetifier=false 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 #Sun Aug 07 22:38:24 CEST 2022
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME 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" val artifact = "crashhandler"
group = "com.dzeio" 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 version = projectVersion
publishing { publishing {
@ -37,6 +37,7 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
buildConfigField("String", "VERSION", "\"$projectVersion\"")
} }
testFixtures { testFixtures {

View File

@ -1,5 +1,6 @@
package com.dzeio.crashhandler package com.dzeio.crashhandler
import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -9,10 +10,16 @@ import android.os.Process
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.edit
import com.dzeio.crashhandler.CrashHandler.Builder import com.dzeio.crashhandler.CrashHandler.Builder
import com.dzeio.crashhandler.ui.ErrorActivity 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 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] * the Crash Handler class, you can get an instance by using it's [Builder]
@ -25,11 +32,24 @@ class CrashHandler private constructor(
@StringRes @StringRes
private val errorReporterCrashKey: Int?, private val errorReporterCrashKey: Int?,
private val prefix: String? = null, 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 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 activity: Class<*>? = ErrorActivity::class.java
private var prefix: String? = null private var prefix: String? = null
private var suffix: String? = null private var suffix: String? = null
private var exportLocation: File? = null
/** /**
* Change the Crash activity to with your own * Change the Crash activity to with your own
@ -124,6 +145,16 @@ class CrashHandler private constructor(
return this 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 * build the Crash Handler
*/ */
@ -135,11 +166,16 @@ class CrashHandler private constructor(
prefsKey, prefsKey,
errorReporterCrashKey, errorReporterCrashKey,
prefix, prefix,
suffix suffix,
exportLocation
) )
} }
} }
init {
instance = this
}
private var oldHandler: Thread.UncaughtExceptionHandler? = null private var oldHandler: Thread.UncaughtExceptionHandler? = null
fun setup() { fun setup() {
@ -179,35 +215,15 @@ class CrashHandler private constructor(
// get current time an date // get current time an date
val now = Date().time val now = Date().time
var previousCrash: Long? = null
// 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)}"
// if lib as access to the preferences store // if lib as access to the preferences store
if (prefs != null && prefsKey != null) { if (prefs != null && prefsKey != null) {
// get the last Crash // get the last Crash
val lastCrash = prefs.getLong(prefsKey, 0L) previousCrash = prefs.getLong(prefsKey, 0L)
// then add it to the logs :D
data += "\nLast crash happened at ${Date(lastCrash)}"
// if a crash already happened just before it means the Error Activity crashed lul // 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 it :D
Log.e( Log.e(
TAG, TAG,
@ -217,7 +233,7 @@ class CrashHandler private constructor(
// try to send a toast indicating it // try to send a toast indicating it
Toast.makeText( Toast.makeText(
application, application,
errorReporterCrashKey ?: R.string.error_reporter_crash, errorReporterCrashKey ?: R.string.crash_handler_reporter_crash,
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
@ -227,18 +243,23 @@ class CrashHandler private constructor(
} }
// update the store // update the store
prefs.edit().putLong(prefsKey, now).commit() prefs.edit(true) { putLong(prefsKey, now) }
} }
Log.i(TAG, "Collecting Error") Log.i(TAG, "Collecting Error")
// get Thread name and ID val data = this.buildData(
data += "\n\nHappened on Thread \"${paramThread.name}\" (${paramThread.id})" now,
previousCrash,
paramThread,
paramThrowable
)
// print exception backtrace try {
data += "\n\nException:\n${paramThrowable.stackTraceToString()}\n\n" exportData(data, now)
} catch (e: IOException) {
data += suffix ?: "" Log.e(TAG, "Could not export the data to file", e)
}
Log.i(TAG, "Starting ${activity.name}") Log.i(TAG, "Starting ${activity.name}")
@ -262,7 +283,127 @@ class CrashHandler private constructor(
// Kill self // Kill self
Process.killProcess(Process.myPid()) 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" android:id="@+id/title"
style="?textAppearanceHeadline5" style="?textAppearanceHeadline5"
android:layout_width="0dp" android:layout_width="0dp"
android:textAlignment="center"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -27,7 +28,8 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="8dp" 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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" /> app:layout_constraintTop_toBottomOf="@+id/title" />
@ -48,7 +50,8 @@
android:id="@+id/error_text" android:id="@+id/error_text"
style="?textAppearanceCaption" style="?textAppearanceCaption"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:textIsSelectable="true" />
</ScrollView> </ScrollView>
@ -59,7 +62,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginBottom="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_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="@+id/error_quit" app:layout_constraintEnd_toEndOf="@+id/error_quit"
app:layout_constraintStart_toStartOf="@+id/error_quit" /> app:layout_constraintStart_toStartOf="@+id/error_quit" />
@ -69,7 +72,7 @@
android:id="@+id/error_quit" android:id="@+id/error_quit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:text="@string/quit" android:text="@string/crash_handler_quit"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />

View File

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Error Activity Translations --> <!-- Error Activity Translations -->
<string name="error_app_crash">Une Erreur est survenu lors de l\'utilisation de l\'application</string> <string name="crash_handler_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="crash_handler_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="crash_handler_copy_to_clipboard">Copier dans le presse papier</string>
<string name="quit">Quitter</string> <string name="crash_handler_quit">Quitter</string>
<string name="error_reporter_crash">Erreur lors de la géneration d\'un rapport d\'erreur</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> </resources>

View File

@ -2,10 +2,16 @@
<resources> <resources>
<!-- Error Activity Translations --> <!-- Error Activity Translations -->
<string name="error_app_crash">An Error Occurred in the application forcing it to close down</string> <string name="crash_handler_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="crash_handler_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="crash_handler_copy_to_clipboard">Copy to clipboard</string>
<string name="quit">Quit</string> <string name="crash_handler_quit">Quit</string>
<string name="error_reporter_crash">An error occurred while making the error report</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> </resources>

View File

@ -3,6 +3,7 @@ package com.dzeio.crashhandlertest
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.dzeio.crashhandler.CrashHandler import com.dzeio.crashhandler.CrashHandler
import com.dzeio.crashhandlertest.ui.ErrorActivity import com.dzeio.crashhandlertest.ui.ErrorActivity
import java.io.File
class Application : android.app.Application() { class Application : android.app.Application() {
override fun onCreate() { override fun onCreate() {
@ -15,17 +16,20 @@ class Application : android.app.Application() {
CrashHandler.Builder() CrashHandler.Builder()
// need the application context to run // need the application context to run
.withContext(this) .withContext(this)
// every other items below are optionnal
// define a custom activity to use // define a custom activity to use
.withActivity(ErrorActivity::class.java) .withActivity(ErrorActivity::class.java)
// define the preferenceManager to be able to handle crash in a custom Activity and to have the previous crash date in the logs // define the preferenceManager to have the previous crash date in the logs
.withPrefs(prefs) .withPrefs(prefs)
.withPrefsKey("com.dzeio.crashhandler.key") .withPrefsKey("com.dzeio.crashhandler.key")
// a Prefix to add at the beginning the crash message // a Prefix to add at the beginning the crash message
.withPrefix( .withPrefix("Prefix")
"POKEMON"
)
// a Suffix to add at the end of the crash message // a Suffix to add at the end of the crash message
.withSuffix("WHYYYYY") .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 & start the module
.build().setup() .build().setup()
} }

View File

@ -8,6 +8,7 @@ import android.os.Process
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.dzeio.crashhandlertest.R
import com.dzeio.crashhandlertest.databinding.ActivityErrorBinding import com.dzeio.crashhandlertest.databinding.ActivityErrorBinding
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -48,14 +49,26 @@ class ErrorActivity : AppCompatActivity() {
val intent = Intent(Intent.ACTION_SEND) val intent = Intent(Intent.ACTION_SEND)
intent.setDataAndType(Uri.parse("mailto:"), "text/plain") intent.setDataAndType(Uri.parse("mailto:"), "text/plain")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("report.openhealth@dzeio.com")) intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("report.openhealth@dzeio.com"))
intent.putExtra(Intent.EXTRA_SUBJECT, "Error report for application crash") intent.putExtra(
intent.putExtra(Intent.EXTRA_TEXT, "Send Report Email\n$data") 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 // send intent
try { try {
startActivity(Intent.createChooser(intent, "Send Report Email...")) startActivity(
Intent.createChooser(
intent,
getString(R.string.send_email_report)
)
)
} catch (e: ActivityNotFoundException) { } 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()
} }
} }

View File

@ -1,16 +1,41 @@
package com.dzeio.crashhandlertest.ui package com.dzeio.crashhandlertest.ui
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.dzeio.crashhandler.CrashHandler
import com.dzeio.crashhandlertest.R import com.dzeio.crashhandlertest.R
import com.dzeio.crashhandlertest.databinding.ActivityMainBinding import com.dzeio.crashhandlertest.databinding.ActivityMainBinding
/**
*
*/
class MainActivity : AppCompatActivity() { 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 private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -24,5 +49,15 @@ class MainActivity : AppCompatActivity() {
// DIE // DIE
throw Exception(getString(R.string.error_message)) 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

@ -8,14 +8,35 @@
tools:context=".ui.MainActivity" tools:context=".ui.MainActivity"
android:padding="16dp"> android:padding="16dp">
<Button <LinearLayout
android:id="@+id/button_first" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/crash_app" android:orientation="vertical"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<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> </androidx.constraintlayout.widget.ConstraintLayout>

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,10 +1,16 @@
<resources> <resources>
<string name="app_name" translatable="false">Crash Handler</string> <string name="app_name" translatable="false">Crash Handler</string>
<string name="crash_app" translatable="false">Crash app</string> <string name="crash_app">Crash app</string>
<string name="error_message" translatable="false">I am an error</string> <string name="error_message">I am an error</string>
<string name="crash_handler_page_title" translatable="false">Crash Handler page title</string> <string name="crash_handler_page_title">Crash Handler page title</string>
<string name="crash_handler_page_subtitle" translatable="false">crash handler page subtitle</string> <string name="crash_handler_page_subtitle">crash handler page subtitle</string>
<string name="github" translatable="false">Github</string> <string name="github" translatable="false">Github</string>
<string name="e_mail" translatable="false">E-Mail</string> <string name="e_mail">E-Mail</string>
<string name="quit" translatable="false">Quit</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> </resources>