Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

66 changed files with 622 additions and 1121 deletions

View File

@ -1,3 +0,0 @@
<!--
Thanks for your Pull Request, Please provide the related Issue using "Fix #0" or describe the change(s) you made.
-->

View File

@ -1,45 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: daily
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
- package-ecosystem: gradle
directory: "/library"
schedule:
interval: daily
commit-message:
prefix: build
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
- package-ecosystem: gradle
directory: "/sample"
schedule:
interval: daily
commit-message:
prefix: build
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
commit-message:
prefix: build
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]

View File

@ -1,50 +0,0 @@
# 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.
name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Build project
run: ./gradlew assembleDebug
- name: Zip artifacts
run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: assemble
path: assemble.zip
- name: ktlint
uses: ScaCap/action-ktlint@master
with:
github_token: ${{ secrets.github_token }}
android: true
reporter: github-pr-review # Change reporter

View File

@ -1,19 +1,3 @@
<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
@ -23,43 +7,8 @@ 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.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()
```
- (Gradle Kotlin DSL) Add `implementation("com.dzeio:crashhandler:1.0.0")`
- (Gradle Groovy DSL) Add `implementation "com.dzeio:crashhandler:1.0.0" `
## Build

View File

@ -20,10 +20,7 @@ android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
@ -42,14 +39,11 @@ android {
dependencies {
implementation(project(":library"))
implementation(project(":crashhandler"))
// Material Design
implementation("com.google.android.material:material:1.8.0")
implementation("com.google.android.material:material:1.6.1")
// Navigation because I don't want to maintain basic transactions and shit
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
// preferences
implementation("androidx.preference:preference-ktx:1.2.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
}

View File

@ -15,10 +15,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.ErrorActivity"
android:theme="@style/Theme.CrashHandler"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,14 @@
package com.dzeio.crashhandlertest
import com.dzeio.crashhandler.CrashHandler
class Application : android.app.Application() {
override fun onCreate() {
super.onCreate()
CrashHandler.Builder()
.withPrefix("Pouet :D")
.withSuffix("WHYYYYY")
.build().setup(this)
}
}

View File

@ -0,0 +1,19 @@
package com.dzeio.crashhandlertest.ui
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import com.dzeio.crashhandlertest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}

View File

@ -0,0 +1,44 @@
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

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
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" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,21 @@
<?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,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,13 @@
<?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,9 @@
<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>
</resources>

View File

@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.1.1")
classpath("com.android.tools.build:gradle:7.2.2")
// 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 "8.1.1" apply false
id("com.android.library") version "8.1.1" apply false
id("com.android.application") version "7.2.2" apply false
id("com.android.library") version "7.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.7.0" apply false
}

View File

@ -6,7 +6,7 @@ plugins {
val artifact = "crashhandler"
group = "com.dzeio"
val projectVersion = project.findProperty("version") as String? ?: "1.1.0"
val projectVersion = project.findProperty("version") as String? ?: "1.0.0"
version = projectVersion
publishing {
@ -24,7 +24,7 @@ publishing {
}
android {
namespace = "$group.$artifact"
namespace = "com.dzeio.crashhandler"
compileSdk = 33
buildToolsVersion = "33.0.0"
@ -35,9 +35,9 @@ android {
minCompileSdk = 21
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
buildConfigField("String", "VERSION", "\"$projectVersion\"")
}
testFixtures {
@ -77,5 +77,5 @@ android {
dependencies {
// Necessary for the Activity (well to make it pretty :D)
implementation("com.google.android.material:material:1.8.0")
implementation("com.google.android.material:material:1.6.1")
}

View File

@ -0,0 +1,224 @@
package com.dzeio.crashhandler
import android.app.Application
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Process
import android.util.Log
import android.widget.Toast
import androidx.annotation.StringRes
import com.dzeio.crashhandler.CrashHandler.Builder
import com.dzeio.crashhandler.ui.ErrorActivity
import java.util.Date
import kotlin.system.exitProcess
/**
* the Crash Handler class, you can get an instance by using it's [Builder]
*/
class CrashHandler private constructor(
private val activity: Any,
private val prefs: SharedPreferences?,
private val prefsKey: String?,
@StringRes
private val errorReporterCrashKey: Int?,
private var prefix: String? = null,
private var suffix: String? = null
) {
private companion object {
private const val TAG = "CrashHandler"
}
/**
* Builder for the crash handler
*/
class Builder() {
private var prefs: SharedPreferences? = null
private var prefsKey: String? = null
private var errorReporterCrashKey: Int? = null
private var activity: Any? = ErrorActivity::class.java
private var prefix: String? = null
private var suffix: String? = null
/**
* Change the Crash activity to with your own
*
* note: you can get the backtrace text by using `intent.getStringExtra("error")`
*
* @param activity the activity class to use
*/
fun withActivity(activity: Any): Builder {
this.activity = activity
return this
}
/**
* [SharedPreferences] of your app to be able to handle ErrorActivity crashes
*
* note: you also need to use [withPrefsKey]
*
* @param prefs instance of [SharedPreferences] to use
*/
fun withPrefs(prefs: SharedPreferences?): Builder {
this.prefs = prefs
return this
}
/**
* the key of the [SharedPreferences] you want to let the library handle
*
* note: you also need to use [withPrefs]
*
* @param prefsKey the key to use
*/
fun withPrefsKey(prefsKey: String?): Builder {
this.prefsKey = prefsKey
return this
}
/**
* the resource key to use for the [Toast] if ErrorActivity crashed
*
* @param errorReporterCrashKey the string key to use
*/
fun witheErrorReporterCrashKey(@StringRes errorReporterCrashKey: Int): Builder {
this.errorReporterCrashKey = errorReporterCrashKey
return this
}
/**
* text to add after the "Crash report:" text and before the rest
*
* ex: "${BuildConfig.APPLICATION_ID} v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
*
* @param prefix the text you add
*/
fun withPrefix(prefix: String): Builder {
this.prefix = prefix
return this
}
/**
* text to add after the content generated by the handler
*
* @param suffix the text
*/
fun withSuffix(suffix: String): Builder {
this.suffix = suffix
return this
}
/**
* build the Crash Handler
*/
fun build(): CrashHandler {
return CrashHandler(activity!!, prefs, prefsKey, errorReporterCrashKey, prefix, suffix)
}
}
/**
* Setup the crash handler, after this method is executed crashes should be handled through your
* activity
*
* @param application the application instance to make sure everything is setup right
*/
fun setup(application: Application) {
// Application Error Handling
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
// Log error to logcat if it wasn't done before has it can not be logged depending on the version
Log.e(TAG, "En error was detected", paramThrowable)
// mostly unusable data but also log Thread Stacktrace
Log.i(TAG, "Thread StackTrace:")
for (item in paramThread.stackTrace) {
Log.i(TAG, item.toString())
}
// 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)}"
// 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)}"
// if a crash already happened just before it means the Error Activity crashed lul
if (lastCrash >= now - 1000) {
// log it :D
Log.e(
TAG,
"Seems like the ErrorActivity also crashed, letting the OS handle it"
)
// try to send a toast indicating it
Toast.makeText(
application,
errorReporterCrashKey ?: R.string.error_reporter_crash,
Toast.LENGTH_LONG
).show()
// Use the default exception handler
oldHandler?.uncaughtException(paramThread, paramThrowable)
return@setDefaultUncaughtExceptionHandler
}
// update the store
prefs.edit().putLong(prefsKey, now).apply()
}
Log.i(TAG, "Collecting Error")
// get Thread name and ID
data += "\n\nHappened on Thread \"${paramThread.name}\" (${paramThread.id})"
// print exception backtrace
data += "\n\nException:\n${paramThrowable.stackTraceToString()}\n\n"
data += suffix ?: ""
Log.i(TAG, "Starting ${(activity as Class<*>).name}")
// prepare the activity
val intent = Intent(application.applicationContext, activity)
// add flags so that it don't use the current Application context
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
// add the Data String
intent.putExtra("error", data)
// Start the activity
application.startActivity(intent)
Log.i(TAG, "Activity should have started")
// Kill self
Process.killProcess(Process.myPid())
exitProcess(10)
}
}
}

View File

@ -5,26 +5,24 @@ import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.dzeio.crashhandler.databinding.CrashHandlerActivityErrorBinding
import com.dzeio.crashhandler.databinding.ActivityErrorBinding
class ErrorActivity : AppCompatActivity() {
private lateinit var binding: CrashHandlerActivityErrorBinding
private lateinit var binding: ActivityErrorBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CrashHandlerActivityErrorBinding.inflate(layoutInflater)
binding = ActivityErrorBinding.inflate(layoutInflater)
setContentView(binding.root)
val data = intent.getStringExtra("error")
// put it in the textView
binding.errorText.apply {
text = data
setTextIsSelectable(true)
}
binding.errorText.text = data
binding.errorText.setTextIsSelectable(true)
// Handle the Quit button
binding.errorQuit.setOnClickListener {

View File

@ -2,8 +2,7 @@
<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"
tools:context=".ui.ErrorActivity"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -11,12 +10,11 @@
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/crash_handler_error_app_crash"
android:text="@string/error_app_crash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -28,8 +26,7 @@
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:text="@string/crash_handler_error_app_report"
android:text="@string/error_app_report"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
@ -50,8 +47,7 @@
android:id="@+id/error_text"
style="?textAppearanceCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true" />
android:layout_height="wrap_content" />
</ScrollView>
@ -62,7 +58,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:text="@string/crash_handler_copy_to_clipboard"
android:text="@string/copy_to_clipboard"
app:layout_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="@+id/error_quit"
app:layout_constraintStart_toStartOf="@+id/error_quit" />
@ -72,7 +68,7 @@
android:id="@+id/error_quit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/crash_handler_quit"
android:text="@string/quit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

View File

@ -0,0 +1,9 @@
<?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>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
</resources>

View File

@ -4,39 +4,15 @@
# 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:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -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
# enable non transitive R Classes
#Tue Jul 19 18:42:00 CEST 2022
android.nonTransitiveRClass=true
# use official kotlin style
kotlin.code.style=official
# use androidX
android.useAndroidX=true
# Disable Jetifier
android.enableJetifier=false
# 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
org.gradle.unsafe.configuration-cache=true

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-8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

0
gradlew vendored Executable file → Normal file
View File

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

View File

@ -1,5 +0,0 @@
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

@ -1,409 +0,0 @@
package com.dzeio.crashhandler
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
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 java.util.TimeZone
/**
* the Crash Handler class, you can get an instance by using it's [Builder]
*/
class CrashHandler private constructor(
private val application: Application?,
private val activity: Class<*>,
private val prefs: SharedPreferences?,
private val prefsKey: String?,
@StringRes
private val errorReporterCrashKey: Int?,
private val prefix: String? = null,
private val suffix: String? = null,
private val exportFolder: File? = null
) {
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!!
}
}
/**
* Builder for the crash handler
*/
class Builder {
private var application: Application? = null
private var prefs: SharedPreferences? = null
private var prefsKey: String? = null
private var errorReporterCrashKey: Int? = null
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
*
* note: you can get the backtrace text by using `intent.getStringExtra("error")`
*
* @param context the context class to use
*/
fun withContext(context: Context): Builder {
this.application = context.applicationContext as Application?
return this
}
/**
* Change the Crash activity to with your own
*
* note: you can get the backtrace text by using `intent.getStringExtra("error")`
*
* @param activity the activity class to use
*/
fun withActivity(activity: Class<*>): Builder {
this.activity = activity
return this
}
/**
* [SharedPreferences] of your app to be able to handle ErrorActivity crashes
*
* note: you also need to use [withPrefsKey]
*
* @param prefs instance of [SharedPreferences] to use
*/
fun withPrefs(prefs: SharedPreferences?): Builder {
this.prefs = prefs
return this
}
/**
* the key of the [SharedPreferences] you want to let the library handle
*
* note: you also need to use [withPrefs]
*
* @param prefsKey the key to use
*/
fun withPrefsKey(prefsKey: String?): Builder {
this.prefsKey = prefsKey
return this
}
/**
* the resource key to use for the [Toast] if ErrorActivity crashed
*
* @param errorReporterCrashKey the string key to use
*/
fun witheErrorReporterCrashKey(@StringRes errorReporterCrashKey: Int): Builder {
this.errorReporterCrashKey = errorReporterCrashKey
return this
}
/**
* text to add after the "Crash report:" text and before the rest
*
* ex: "${BuildConfig.APPLICATION_ID} v${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
*
* @param prefix the text you add
*/
fun withPrefix(prefix: String): Builder {
this.prefix = prefix
return this
}
/**
* text to add after the content generated by the handler
*
* @param suffix the text
*/
fun withSuffix(suffix: String): Builder {
this.suffix = suffix
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
*/
fun build(): CrashHandler {
return CrashHandler(
application,
activity!!,
prefs,
prefsKey,
errorReporterCrashKey,
prefix,
suffix,
exportLocation
)
}
}
init {
instance = this
}
private var oldHandler: Thread.UncaughtExceptionHandler? = null
fun setup() {
if (application != null) {
this.setup(application)
}
}
/**
* Destroy the handler
*/
fun destroy() {
if (oldHandler != null) {
Thread.setDefaultUncaughtExceptionHandler(oldHandler)
}
}
/**
* Setup the crash handler, after this method is executed crashes should be handled through your
* activity
*
* @param application the application instance to make sure everything is setup right
*/
fun setup(application: Application) {
// Application Error Handling
oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { paramThread, paramThrowable ->
// Log error to logcat if it wasn't done before has it can not be logged depending on the version
Log.e(TAG, "En error was detected", paramThrowable)
// mostly unusable data but also log Thread Stacktrace
Log.i(TAG, "Thread StackTrace:")
for (item in paramThread.stackTrace) {
Log.i(TAG, item.toString())
}
// get current time an date
val now = Date().time
var previousCrash: Long? = null
// if lib as access to the preferences store
if (prefs != null && prefsKey != null) {
// get the last Crash
previousCrash = prefs.getLong(prefsKey, 0L)
// if a crash already happened just before it means the Error Activity crashed lul
if (previousCrash >= now - 1000) {
// log it :D
Log.e(
TAG,
"Seems like the ErrorActivity also crashed, letting the OS handle it"
)
// try to send a toast indicating it
Toast.makeText(
application,
errorReporterCrashKey ?: R.string.crash_handler_reporter_crash,
Toast.LENGTH_LONG
).show()
// Use the default exception handler
oldHandler?.uncaughtException(paramThread, paramThrowable)
return@setDefaultUncaughtExceptionHandler
}
// update the store
prefs.edit(true) { putLong(prefsKey, now) }
}
Log.i(TAG, "Collecting Error")
val data = this.buildData(
now,
previousCrash,
paramThread,
paramThrowable
)
try {
exportData(data, now)
} catch (e: IOException) {
Log.e(TAG, "Could not export the data to file", e)
}
Log.i(TAG, "Starting ${activity.name}")
// prepare the activity
val intent = Intent(application, activity)
// add flags so that it don't use the current Application context
intent.addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
)
// add the Data String
intent.putExtra("error", data)
// Start the activity
application.startActivity(intent)
Log.i(TAG, "Activity should have started")
// Kill self
Process.killProcess(Process.myPid())
}
}
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

@ -1,71 +0,0 @@
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

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error Activity Translations -->
<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

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error Activity Translations -->
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -1,36 +0,0 @@
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()
// 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()
}
}

View File

@ -1,91 +0,0 @@
package com.dzeio.crashhandlertest.ui
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
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")
// put it in the textView
binding.errorText.text = data
// Handle the Quit button
binding.errorQuit.setOnClickListener {
Process.killProcess(Process.myPid())
exitProcess(10)
}
// Handle the Email Button
binding.errorSubmitEmail.setOnClickListener {
// Create Intent
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,
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,
getString(R.string.send_email_report)
)
)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
this,
getString(R.string.no_email_client_found),
Toast.LENGTH_LONG
).show()
}
}
// Handle the GitHub Button
binding.errorSubmitGithub.setOnClickListener {
// Build URL
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))
)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, "No Web Browser found :(", Toast.LENGTH_LONG).show()
}
}
}
}

View File

@ -1,63 +0,0 @@
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?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
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,23 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="48"
android:viewportHeight="48"
android:autoMirrored="true">
<group android:scaleX="0.96130437"
android:scaleY="0.96130437"
android:translateX="0.9286957"
android:translateY="0.9286957">
<group android:scaleX="0.99"
android:scaleY="0.99"
android:translateX="0.24"
android:translateY="0.24">
<group android:scaleX="0.495"
android:scaleY="0.495"
android:translateX="12.12"
android:translateY="12.12">
<path android:fillColor="#FF4CAF50" android:pathData="M13.05,31.9q0.6,0 1.05,-0.45 0.45,-0.45 0.45,-1.05 0,-0.6 -0.45,-1.05 -0.45,-0.45 -1.05,-0.45 -0.6,0 -1.05,0.45 -0.45,0.45 -0.45,1.05 0,0.6 0.45,1.05 0.45,0.45 1.05,0.45ZM11.55,25.4h3v-9.55h-3ZM20.5,29.4h15.95v-3L20.5,26.4ZM20.5,20.85h15.95v-3L20.5,17.85ZM6.6,40q-1.2,0 -2.1,-0.9 -0.9,-0.9 -0.9,-2.1L3.6,11q0,-1.2 0.9,-2.1 0.9,-0.9 2.1,-0.9h34.8q1.2,0 2.1,0.9 0.9,0.9 0.9,2.1v26q0,1.2 -0.9,2.1 -0.9,0.9 -2.1,0.9Z"/>
</group>
</group>
</group>
</vector>

View File

@ -1,82 +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"
android:fitsSystemWindows="true"
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView3"
style="?textAppearanceHeadline5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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" />
<TextView
android:id="@+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
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" />
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/error_submit_email"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4">
<TextView
android:id="@+id/error_text"
android:layout_width="match_parent"
style="?textAppearanceCaption"
android:layout_height="wrap_content"
android:textIsSelectable="true" />
</ScrollView>
<Button
android:id="@+id/error_submit_github"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/github"
android:layout_marginStart="16dp"
app:layout_constraintBaseline_toBaselineOf="@+id/error_submit_email"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/error_submit_email"
android:layout_marginBottom="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="@string/e_mail"
app:layout_constraintBottom_toTopOf="@+id/error_quit"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/error_quit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:text="@string/quit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,42 +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"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
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">
<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,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,15 +0,0 @@
<?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,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -1,16 +0,0 @@
<resources>
<string name="app_name" translatable="false">Crash Handler</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>

View File

@ -15,5 +15,5 @@ dependencyResolutionManagement {
rootProject.name = "Crash Handler"
include ':sample'
include ':library'
include ':app'
include ':crashhandler'