Added crash reporting.

This commit is contained in:
Erik C. Thauvin 2017-09-01 13:19:09 -07:00
parent 3b257fb15b
commit ea346de4df
5 changed files with 249 additions and 0 deletions

View file

@ -0,0 +1,39 @@
/*
* App.kt
*
* Copyright 2016-2017 Erik C. Thauvin (erik@thauvin.net)
*
* 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
*
* http://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.
*/
package net.thauvin.erik.android.tesremoteprogrammer
import android.app.Application
import android.content.Context
import net.thauvin.erik.android.tesremoteprogrammer.reporting.CrashEmailFactory
import net.thauvin.erik.android.tesremoteprogrammer.reporting.CrashReportActivity
import org.acra.ACRA
import org.acra.ReportingInteractionMode
import org.acra.annotation.ReportsCrashes
@ReportsCrashes(mailTo = "erik@thauvin.net",
mode = ReportingInteractionMode.DIALOG,
reportSenderFactoryClasses = arrayOf(CrashEmailFactory::class),
reportDialogClass = CrashReportActivity::class)
open class App : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
ACRA.init(this)
}
}

View file

@ -0,0 +1,70 @@
/*
* CrashEmail.kt
*
* Copyright 2016-2017 Erik C. Thauvin (erik@thauvin.net)
*
* 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
*
* http://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.
*/
package net.thauvin.erik.android.tesremoteprogrammer.reporting
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import net.thauvin.erik.android.tesremoteprogrammer.R
import org.acra.ACRAConstants
import org.acra.ReportField
import org.acra.collections.ImmutableSet
import org.acra.collector.CrashReportData
import org.acra.config.ACRAConfiguration
import org.acra.sender.ReportSender
import org.acra.sender.ReportSenderException
class CrashEmail(private val config: ACRAConfiguration) : ReportSender {
@Throws(ReportSenderException::class)
override fun send(context: Context, errorContent: CrashReportData) {
val subject = context.getString(R.string.crash_report_subject,
context.getString(R.string.app_name))
val body = buildBody(errorContent)
val emailIntent = Intent(android.content.Intent.ACTION_SENDTO)
emailIntent.data = Uri.fromParts("mailto", config.mailTo(), null)
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject)
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, body)
context.startActivity(emailIntent)
}
private fun buildBody(errorContent: CrashReportData): String {
var fields: Set<ReportField> = config.reportFields
if (fields.isEmpty()) {
fields = ImmutableSet(*ACRAConstants.DEFAULT_MAIL_REPORT_FIELDS)
}
val builder = StringBuilder()
for (field in fields) {
if (field == ReportField.USER_COMMENT || field == ReportField.STACK_TRACE) {
builder.append("-- ").append(field.toString()).append(" --\n")
} else {
builder.append(field.toString()).append('=')
}
val value = errorContent[field]
if (value != null) {
builder.append(TextUtils.join("\n\t", value.flatten()))
}
builder.append('\n')
}
return builder.toString()
}
}

View file

@ -0,0 +1,28 @@
/*
* CrashEmailFactory.kt
*
* Copyright 2016-2017 Erik C. Thauvin (erik@thauvin.net)
*
* 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
*
* http://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.
*/
package net.thauvin.erik.android.tesremoteprogrammer.reporting
import android.content.Context
import org.acra.config.ACRAConfiguration
import org.acra.sender.ReportSender
import org.acra.sender.ReportSenderFactory
class CrashEmailFactory : ReportSenderFactory {
override fun create(context: Context, config: ACRAConfiguration): ReportSender =
CrashEmail(config)
}

View file

@ -0,0 +1,75 @@
/*
* CrashReportActivity.kt
*
* Copyright 2016-2017 Erik C. Thauvin (erik@thauvin.net)
*
* 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
*
* http://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.
*/
package net.thauvin.erik.android.tesremoteprogrammer.reporting
import android.content.DialogInterface
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.widget.EditText
import net.thauvin.erik.android.tesremoteprogrammer.R
import org.acra.dialog.BaseCrashReportDialog
import org.jetbrains.anko.find
class CrashReportActivity : BaseCrashReportDialog(), DialogInterface.OnDismissListener,
DialogInterface.OnClickListener {
private var comment: EditText? = null
companion object {
private val STATE_USER_COMMENT = "comment"
}
override fun init(savedInstanceState: Bundle?) {
super.init(savedInstanceState)
val dialog = AlertDialog.Builder(this)
.setTitle(getString(R.string.crash_dialog_title, getString(R.string.app_name)))
.setView(R.layout.crash_report_dialog)
.setPositiveButton(R.string.ok, this)
.setNegativeButton(R.string.cancel, this)
.create()
dialog.setCanceledOnTouchOutside(false)
dialog.setOnDismissListener(this)
dialog.show()
comment = dialog.find(android.R.id.input)
if (savedInstanceState != null) {
comment!!.setText(savedInstanceState.getString(STATE_USER_COMMENT))
}
}
override fun onDismiss(dialog: DialogInterface) {
finish()
}
override fun onClick(dialog: DialogInterface, which: Int) {
if (which == DialogInterface.BUTTON_POSITIVE) {
sendCrash(comment!!.text.toString(), "")
} else {
cancelReports()
}
finish()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString(STATE_USER_COMMENT, comment!!.text.toString())
super.onSaveInstanceState(outState)
}
}

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="24dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="20dp">
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/crash_dialog_text" />
<TextView
android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/crash_dialog_comment_prompt" />
<EditText
android:id="@android:id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="textMultiLine" />
</LinearLayout>
</ScrollView>