You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
10 KiB
Kotlin
203 lines
10 KiB
Kotlin
|
3 years ago
|
/*
|
||
|
|
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
|
||
|
|
*/
|
||
|
|
|
||
|
3 years ago
|
package com.fox2code.mmm
|
||
|
4 years ago
|
|
||
|
3 years ago
|
import android.annotation.SuppressLint
|
||
|
|
import android.content.ClipData
|
||
|
|
import android.content.ClipboardManager
|
||
|
|
import android.content.DialogInterface
|
||
|
|
import android.os.Bundle
|
||
|
|
import android.view.View
|
||
|
|
import android.widget.EditText
|
||
|
|
import android.widget.Toast
|
||
|
|
import com.fox2code.foxcompat.app.FoxActivity
|
||
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||
|
|
import com.google.android.material.textview.MaterialTextView
|
||
|
|
import io.sentry.Sentry
|
||
|
|
import io.sentry.UserFeedback
|
||
|
|
import timber.log.Timber
|
||
|
|
import java.io.PrintWriter
|
||
|
|
import java.io.StringWriter
|
||
|
4 years ago
|
|
||
|
3 years ago
|
class CrashHandler : FoxActivity() {
|
||
|
|
@Suppress("DEPRECATION", "KotlinConstantConditions")
|
||
|
4 years ago
|
@SuppressLint("RestrictedApi")
|
||
|
3 years ago
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||
|
|
Timber.i("CrashHandler.onCreate(%s)", savedInstanceState)
|
||
|
4 years ago
|
// log intent with extras
|
||
|
3 years ago
|
Timber.d("CrashHandler.onCreate: intent=%s", intent)
|
||
|
|
super.onCreate(savedInstanceState)
|
||
|
|
setContentView(R.layout.activity_crash_handler)
|
||
|
4 years ago
|
// set crash_details MaterialTextView to the exception passed in the intent or unknown if null
|
||
|
|
// convert stacktrace from array to string, and pretty print it (first line is the exception, the rest is the stacktrace, with each line indented by 4 spaces)
|
||
|
3 years ago
|
val crashDetails = findViewById<MaterialTextView>(R.id.crash_details)
|
||
|
|
crashDetails.text = ""
|
||
|
4 years ago
|
// get the exception from the intent
|
||
|
3 years ago
|
val exception = intent.getSerializableExtra("exception") as Throwable?
|
||
|
4 years ago
|
// get the crashReportingEnabled from the intent
|
||
|
3 years ago
|
val crashReportingEnabled = intent.getBooleanExtra("crashReportingEnabled", false)
|
||
|
4 years ago
|
// if the exception is null, set the crash details to "Unknown"
|
||
|
|
if (exception == null) {
|
||
|
3 years ago
|
crashDetails.setText(R.string.crash_details)
|
||
|
4 years ago
|
} else {
|
||
|
|
// if the exception is not null, set the crash details to the exception and stacktrace
|
||
|
|
// stacktrace is an StacktraceElement, so convert it to a string and replace the commas with newlines
|
||
|
3 years ago
|
val stringWriter = StringWriter()
|
||
|
|
exception.printStackTrace(PrintWriter(stringWriter))
|
||
|
|
var stacktrace = stringWriter.toString()
|
||
|
|
stacktrace = stacktrace.replace(",", "\n ")
|
||
|
|
crashDetails.text = getString(R.string.crash_full_stacktrace, stacktrace)
|
||
|
4 years ago
|
}
|
||
|
3 years ago
|
val lastEventId = intent.getStringExtra("lastEventId")
|
||
|
|
Timber.d(
|
||
|
|
"CrashHandler.onCreate: lastEventId=%s, crashReportingEnabled=%s",
|
||
|
|
lastEventId,
|
||
|
|
crashReportingEnabled
|
||
|
|
)
|
||
|
4 years ago
|
if (lastEventId == null && crashReportingEnabled) {
|
||
|
|
// if lastEventId is null, hide the feedback button
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback).visibility = View.GONE
|
||
|
|
Timber.d("CrashHandler.onCreate: lastEventId is null but crash reporting is enabled. This may indicate a bug in the crash reporting system.")
|
||
|
4 years ago
|
} else {
|
||
|
|
// if lastEventId is not null, show the feedback button
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback).visibility = View.VISIBLE
|
||
|
4 years ago
|
}
|
||
|
4 years ago
|
// disable feedback if sentry is disabled
|
||
|
3 years ago
|
if (crashReportingEnabled && lastEventId != null) {
|
||
|
4 years ago
|
// get name, email, and message fields
|
||
|
3 years ago
|
val name = findViewById<EditText>(R.id.feedback_name)
|
||
|
|
val email = findViewById<EditText>(R.id.feedback_email)
|
||
|
|
val description = findViewById<EditText>(R.id.feedback_message)
|
||
|
4 years ago
|
// get submit button
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback_submit).setOnClickListener { _: View? ->
|
||
|
4 years ago
|
// require the feedback_message, rest is optional
|
||
|
3 years ago
|
if (description.text.toString() == "") {
|
||
|
|
Toast.makeText(this, R.string.sentry_dialogue_empty_message, Toast.LENGTH_LONG)
|
||
|
|
.show()
|
||
|
|
return@setOnClickListener
|
||
|
4 years ago
|
}
|
||
|
|
// if email or name is empty, use "Anonymous"
|
||
|
3 years ago
|
val nameString =
|
||
|
|
arrayOf(if (name.text.toString() == "") "Anonymous" else name.text.toString())
|
||
|
|
val emailString =
|
||
|
|
arrayOf(if (email.text.toString() == "") "Anonymous" else email.text.toString())
|
||
|
3 years ago
|
// get sentryException passed in intent
|
||
|
3 years ago
|
val sentryException = intent.getSerializableExtra("sentryException") as Throwable?
|
||
|
|
Thread {
|
||
|
4 years ago
|
try {
|
||
|
3 years ago
|
val userFeedback: UserFeedback
|
||
|
3 years ago
|
if (sentryException != null) {
|
||
|
3 years ago
|
userFeedback = UserFeedback(Sentry.captureException(sentryException))
|
||
|
3 years ago
|
// Setups the JSON body
|
||
|
3 years ago
|
if (nameString[0] == "") nameString[0] = "Anonymous"
|
||
|
|
if (emailString[0] == "") emailString[0] = "Anonymous"
|
||
|
|
userFeedback.name = nameString[0]
|
||
|
|
userFeedback.email = emailString[0]
|
||
|
|
userFeedback.comments = description.text.toString()
|
||
|
|
Sentry.captureUserFeedback(userFeedback)
|
||
|
|
}
|
||
|
|
Timber.i(
|
||
|
|
"Submitted user feedback: name %s email %s comment %s",
|
||
|
|
nameString[0],
|
||
|
|
emailString[0],
|
||
|
|
description.text.toString()
|
||
|
|
)
|
||
|
|
runOnUiThread {
|
||
|
|
Toast.makeText(
|
||
|
|
this,
|
||
|
|
R.string.sentry_dialogue_success,
|
||
|
|
Toast.LENGTH_LONG
|
||
|
|
).show()
|
||
|
3 years ago
|
}
|
||
|
3 years ago
|
// Close the activity
|
||
|
3 years ago
|
finish()
|
||
|
3 years ago
|
// start the main activity
|
||
|
3 years ago
|
startActivity(packageManager.getLaunchIntentForPackage(packageName))
|
||
|
|
} catch (e: Exception) {
|
||
|
|
Timber.e(e, "Failed to submit user feedback")
|
||
|
4 years ago
|
// Show a toast if the user feedback could not be submitted
|
||
|
3 years ago
|
runOnUiThread {
|
||
|
|
Toast.makeText(
|
||
|
|
this,
|
||
|
|
R.string.sentry_dialogue_failed_toast,
|
||
|
|
Toast.LENGTH_LONG
|
||
|
|
).show()
|
||
|
|
}
|
||
|
4 years ago
|
}
|
||
|
3 years ago
|
}.start()
|
||
|
|
}
|
||
|
4 years ago
|
// get restart button
|
||
|
3 years ago
|
findViewById<View>(R.id.restart).setOnClickListener { _: View? ->
|
||
|
3 years ago
|
// Restart the app and submit sans feedback
|
||
|
3 years ago
|
val sentryException = intent.getSerializableExtra("sentryException") as Throwable?
|
||
|
|
if (crashReportingEnabled) Sentry.captureException(sentryException!!)
|
||
|
|
finish()
|
||
|
|
startActivity(packageManager.getLaunchIntentForPackage(packageName))
|
||
|
|
}
|
||
|
4 years ago
|
} else {
|
||
|
|
// disable feedback if sentry is disabled
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback_name).isEnabled = false
|
||
|
|
findViewById<View>(R.id.feedback_email).isEnabled = false
|
||
|
|
findViewById<View>(R.id.feedback_message).isEnabled = false
|
||
|
4 years ago
|
// fade out all the fields
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback_name).alpha = 0.5f
|
||
|
|
findViewById<View>(R.id.feedback_email).alpha = 0.5f
|
||
|
|
findViewById<View>(R.id.feedback_message).alpha = 0.5f
|
||
|
4 years ago
|
// fade out the submit button
|
||
|
3 years ago
|
findViewById<View>(R.id.feedback_submit).alpha = 0.5f
|
||
|
4 years ago
|
// set feedback_text to "Crash reporting is disabled"
|
||
|
3 years ago
|
(findViewById<View>(R.id.feedback_text) as MaterialTextView).setText(R.string.sentry_enable_nag)
|
||
|
|
findViewById<View>(R.id.feedback_submit).setOnClickListener { _: View? ->
|
||
|
|
Toast.makeText(
|
||
|
|
this,
|
||
|
|
R.string.sentry_dialogue_disabled,
|
||
|
|
Toast.LENGTH_LONG
|
||
|
|
).show()
|
||
|
|
}
|
||
|
4 years ago
|
// handle restart button
|
||
|
|
// we have to explicitly enable it because it's disabled by default
|
||
|
3 years ago
|
findViewById<View>(R.id.restart).isEnabled = true
|
||
|
|
findViewById<View>(R.id.restart).setOnClickListener { _: View? ->
|
||
|
4 years ago
|
// Restart the app
|
||
|
3 years ago
|
finish()
|
||
|
|
startActivity(packageManager.getLaunchIntentForPackage(packageName))
|
||
|
|
}
|
||
|
4 years ago
|
}
|
||
|
4 years ago
|
// handle reset button
|
||
|
3 years ago
|
findViewById<View>(R.id.reset).setOnClickListener { _: View? ->
|
||
|
4 years ago
|
// show a confirmation material dialog
|
||
|
3 years ago
|
val builder = MaterialAlertDialogBuilder(this)
|
||
|
|
builder.setTitle(R.string.reset_app)
|
||
|
|
builder.setMessage(R.string.reset_app_confirmation)
|
||
|
|
builder.setPositiveButton(R.string.reset) { _: DialogInterface?, _: Int ->
|
||
|
4 years ago
|
// reset the app
|
||
|
3 years ago
|
MainApplication.INSTANCE!!.resetApp()
|
||
|
3 years ago
|
}
|
||
|
|
builder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int -> }
|
||
|
|
builder.show()
|
||
|
|
}
|
||
|
4 years ago
|
}
|
||
|
|
|
||
|
3 years ago
|
fun copyCrashDetails(view: View) {
|
||
|
4 years ago
|
// change view to a checkmark
|
||
|
3 years ago
|
view.setBackgroundResource(R.drawable.baseline_check_24)
|
||
|
4 years ago
|
// copy crash_details to clipboard
|
||
|
3 years ago
|
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||
|
|
val crashDetails =
|
||
|
|
(findViewById<View>(R.id.crash_details) as MaterialTextView).text.toString()
|
||
|
|
clipboard.setPrimaryClip(ClipData.newPlainText("crash_details", crashDetails))
|
||
|
4 years ago
|
// show a toast
|
||
|
3 years ago
|
Toast.makeText(this, R.string.crash_details_copied, Toast.LENGTH_LONG).show()
|
||
|
4 years ago
|
// after 1 second, change the view back to a copy button
|
||
|
3 years ago
|
Thread {
|
||
|
4 years ago
|
try {
|
||
|
3 years ago
|
Thread.sleep(1000)
|
||
|
|
} catch (e: InterruptedException) {
|
||
|
|
Thread.currentThread().interrupt()
|
||
|
4 years ago
|
}
|
||
|
3 years ago
|
runOnUiThread { view.setBackgroundResource(R.drawable.baseline_copy_all_24) }
|
||
|
|
}.start()
|
||
|
4 years ago
|
}
|
||
|
3 years ago
|
}
|