diff --git a/common/src/main/java/com/itsaky/androidide/utils/FeedbackEmailHandler.kt b/common/src/main/java/com/itsaky/androidide/utils/FeedbackEmailHandler.kt index 5751497103..fc45eced78 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/FeedbackEmailHandler.kt +++ b/common/src/main/java/com/itsaky/androidide/utils/FeedbackEmailHandler.kt @@ -31,9 +31,21 @@ class FeedbackEmailHandler( const val AUTHORITY_SUFFIX = "providers.fileprovider" const val SCREENSHOTS_DIR = "feedback_screenshots" const val LOGS_DIR = "feedback_logs" + const val MAX_EMAIL_BODY_CHARS = 50_000 private val log = LoggerFactory.getLogger(FeedbackEmailHandler::class.java) } + private fun sanitizeEmailBody(body: String, hasLogAttachment: Boolean = true): String { + if (body.length <= MAX_EMAIL_BODY_CHARS) return body + val suffix = if (hasLogAttachment) " See attached file." else "" + return buildString { + append(body.take(MAX_EMAIL_BODY_CHARS)) + append("\n\n---\n(Log truncated: ") + append(body.length - MAX_EMAIL_BODY_CHARS) + append(" chars omitted.$suffix)") + } + } + suspend fun captureAndPrepareScreenshotUri( activity: Activity, ): Uri? { @@ -138,7 +150,8 @@ class FeedbackEmailHandler( emailRecipient = emailRecipient, subject = subject, body = body, - attachmentUris = attachmentUris + attachmentUris = attachmentUris, + hasLogAttachment = logContentUri != null ) } @@ -146,8 +159,10 @@ class FeedbackEmailHandler( emailRecipient: String, subject: String, body: String, - attachmentUris: MutableList + attachmentUris: MutableList, + hasLogAttachment: Boolean = false ): Intent { + val safeBody = sanitizeEmailBody(body, hasLogAttachment) return when { // No screenshot or log file (if both files failed to be created) attachmentUris.isEmpty() -> { @@ -155,7 +170,7 @@ class FeedbackEmailHandler( data = "mailto:".toUri() putExtra(Intent.EXTRA_EMAIL, arrayOf(emailRecipient)) putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, body) + putExtra(Intent.EXTRA_TEXT, safeBody) } } // Screenshot and/or log file @@ -163,7 +178,7 @@ class FeedbackEmailHandler( Intent(Intent.ACTION_SEND_MULTIPLE).apply { putExtra(Intent.EXTRA_EMAIL, arrayOf(emailRecipient)) putExtra(Intent.EXTRA_SUBJECT, subject) - putExtra(Intent.EXTRA_TEXT, body) + putExtra(Intent.EXTRA_TEXT, safeBody) putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(attachmentUris)) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) type = "message/rfc822" diff --git a/common/src/main/java/com/itsaky/androidide/utils/FeedbackManager.kt b/common/src/main/java/com/itsaky/androidide/utils/FeedbackManager.kt index 89272e89dc..5c7dfa9215 100644 --- a/common/src/main/java/com/itsaky/androidide/utils/FeedbackManager.kt +++ b/common/src/main/java/com/itsaky/androidide/utils/FeedbackManager.kt @@ -1,6 +1,7 @@ package com.itsaky.androidide.utils import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.graphics.Bitmap @@ -8,6 +9,7 @@ import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper +import android.os.TransactionTooLargeException import android.view.PixelCopy import android.view.View import android.widget.Toast @@ -19,10 +21,12 @@ import androidx.core.net.toUri import androidx.core.text.HtmlCompat import androidx.lifecycle.lifecycleScope import com.itsaky.androidide.buildinfo.BuildInfo +import com.itsaky.androidide.eventbus.events.editor.ReportCaughtExceptionEvent import com.itsaky.androidide.resources.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.greenrobot.eventbus.EventBus import org.slf4j.LoggerFactory import java.io.File import java.io.FileOutputStream @@ -334,13 +338,30 @@ object FeedbackManager { feedbackBody, ) - if (emailIntent.resolveActivity(activity.packageManager) != null) { + runCatching { activity.startActivity(emailIntent) - } else { - Toast - .makeText(activity, - activity.getString(R.string.no_email_apps), Toast.LENGTH_LONG) - .show() + }.onFailure { e -> + when { + e is ActivityNotFoundException -> { + Toast.makeText(activity, R.string.no_email_apps, Toast.LENGTH_LONG).show() + } + e is TransactionTooLargeException || + (e is RuntimeException && e.cause is TransactionTooLargeException) -> { + logger.error("Intent transaction failed: Data too large", e) + Toast.makeText(activity, R.string.msg_feedback_log_too_long, Toast.LENGTH_LONG).show() + } + else -> { + logger.error("Intent transaction failed: Unknown error", e) + EventBus.getDefault().post( + ReportCaughtExceptionEvent( + throwable = e, + message = "Feedback email intent failed", + extras = mapOf("screen" to getCurrentScreenName(activity)) + ) + ) + Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_LONG).show() + } + } } } } diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index af2cd295f9..622f21b8c4 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -788,6 +788,7 @@ Internet access is required. Code on the Go Feedback for Code on the Go + The log is too large to send directly. No computer? No Internet?\nNo problem. Offline More