diff --git a/app/src/main/java/com/onemoresecret/CrashReportActivity.kt b/app/src/main/java/com/onemoresecret/CrashReportActivity.kt
index 8d2ff23..5d2610c 100644
--- a/app/src/main/java/com/onemoresecret/CrashReportActivity.kt
+++ b/app/src/main/java/com/onemoresecret/CrashReportActivity.kt
@@ -1,8 +1,14 @@
package com.onemoresecret
-import android.content.ActivityNotFoundException
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ClipboardManager
+import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Bundle
+import android.os.PersistableBundle
+import android.view.WindowManager
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -14,38 +20,43 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.core.net.toUri
-import com.onemoresecret.CrashReportData
-import com.onemoresecret.OmsFileProvider
-import com.onemoresecret.R
import com.onemoresecret.composable.OneMoreSecretTheme
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import kotlin.system.exitProcess
+private const val MAX_INLINE_REPORT_LENGTH = 128 * 1024
class CrashReportActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ //prohibit screenshots
+ window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+
Thread { OmsFileProvider.purgeTmp(this) }.start()
@Suppress("DEPRECATION")
val crashReportData = intent.getSerializableExtra(OmsUncaughtExceptionHandler.EXTRA_CRASH_REPORT) as? CrashReportData
-
+
+ if (crashReportData == null) {
+ finish()
+ return
+ }
+
setContent {
OneMoreSecretTheme {
- if (crashReportData != null) {
- CrashReport(
- crashReportData = crashReportData,
- onDismiss = {
- finish()
- exitProcess(0)
- }
- )
- }
+ CrashReport(
+ crashReportData = crashReportData,
+ onDismiss = {
+ finish()
+ exitProcess(0)
+ }
+ )
}
}
}
@@ -105,60 +116,64 @@ fun CrashReport(
Text("Dismiss")
}
Spacer(modifier = Modifier.width(8.dp))
+ TextButton(onClick = {
+ copyToClipboard(context, reportText)
+ }) {
+ Text(stringResource(R.string.copy))
+ }
+ Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
- sendEmail(context, crashReportData, includeLogcat, onDismiss)
+ shareReport(context, reportText)
}) {
- Text("Send")
+ Text(stringResource(R.string.share))
}
}
}
}
-// Extracted logic for sending the email
-private fun sendEmail(
- context: android.content.Context,
- crashReportData: CrashReportData,
- includeLogcat: Boolean,
- onComplete: () -> Unit
-) {
- val contactEmail = context.getString(R.string.contact_email)
+private fun copyToClipboard(context: Context, report: String) {
+ val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clipData = ClipData.newPlainText("oneMoreSecret", report)
- fun createBaseIntent(action: String): Intent {
- return Intent(action).apply {
- data = "mailto:".toUri()
- putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.crash_email_subject))
- putExtra(Intent.EXTRA_EMAIL, arrayOf(contactEmail))
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ clipData.description.extras = PersistableBundle().apply {
+ putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
}
}
- try {
- val crashReport = crashReportData.toString(includeLogcat)
- val fileRecord = OmsFileProvider.create(context, "crash_report.txt", false)
- Files.write(fileRecord.path, crashReport!!.toByteArray(StandardCharsets.UTF_8))
+ clipboardManager.setPrimaryClip(clipData)
+ Toast.makeText(context, context.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show()
+}
- val intentSend = createBaseIntent(Intent.ACTION_SEND).apply {
- putExtra(Intent.EXTRA_STREAM, fileRecord.uri)
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- putExtra(Intent.EXTRA_TEXT, context.getString(R.string.crash_email_body))
- }
+// Extracted logic for sharing the report
+private fun shareReport(context: Context, report: String) {
+ val sendIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.crash_email_subject))
+ putExtra(Intent.EXTRA_TITLE, context.getString(R.string.crash_email_subject))
+ putExtra(Intent.EXTRA_EMAIL, arrayOf(context.getString(R.string.contact_email)))
+ }
- try {
- context.startActivity(intentSend)
- onComplete()
- } catch (_: ActivityNotFoundException) {
- try {
- val intentSendTo = createBaseIntent(Intent.ACTION_SENDTO).apply {
- putExtra(Intent.EXTRA_TEXT, crashReport)
- }
- context.startActivity(intentSendTo)
- onComplete()
- } catch (_: ActivityNotFoundException) {
- Toast.makeText(context,
- context.getString(R.string.could_not_send_email), Toast.LENGTH_LONG).show()
- onComplete()
- }
- }
+ val attached = try {
+ val fileRecord = OmsFileProvider.create(context, "crash_report.txt", false, "crash")
+ Files.write(fileRecord.path, report.toByteArray(StandardCharsets.UTF_8))
+ sendIntent.putExtra(Intent.EXTRA_STREAM, fileRecord.uri)
+ sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ true
} catch (ex: IOException) {
- ex.printStackTrace()
+ Util.printStackTrace(ex)
+ false
}
-}
\ No newline at end of file
+
+ val body = context.getString(R.string.crash_email_body)
+ sendIntent.putExtra(
+ Intent.EXTRA_TEXT,
+ when {
+ report.length <= MAX_INLINE_REPORT_LENGTH -> "$body\n\n$report"
+ attached -> body
+ else -> "$body\n\n" + report.take(MAX_INLINE_REPORT_LENGTH)
+ }
+ )
+
+ context.startActivity(Intent.createChooser(sendIntent, null))
+}
diff --git a/app/src/main/java/com/onemoresecret/CrashReportData.kt b/app/src/main/java/com/onemoresecret/CrashReportData.kt
index b44a678..2c2719f 100644
--- a/app/src/main/java/com/onemoresecret/CrashReportData.kt
+++ b/app/src/main/java/com/onemoresecret/CrashReportData.kt
@@ -10,8 +10,9 @@ import java.io.PrintWriter
import java.io.Serializable
import java.io.StringWriter
-class CrashReportData(private val throwable: Throwable?) : Serializable {
- private val logcat: String? = getLogcat()
+class CrashReportData(throwable: Throwable?, logcatTailLines: Int? = null) : Serializable {
+ private val stackTrace: String? = throwable?.let { renderStackTrace(it) }
+ private val logcat: String? = getLogcat(logcatTailLines)
fun toString(includeLogcat: Boolean): String? {
try {
@@ -24,9 +25,9 @@ class CrashReportData(private val throwable: Throwable?) : Serializable {
BuildConfig.FLAVOR
)
)
- if (throwable != null) {
+ if (stackTrace != null) {
pw.println("\n----- STACK TRACE -----")
- throwable.printStackTrace(pw)
+ pw.println(stackTrace)
}
pw.println("\n----- DEVICE -----")
pw.println("Brand: " + Build.BRAND)
@@ -55,8 +56,19 @@ class CrashReportData(private val throwable: Throwable?) : Serializable {
companion object {
private val TAG: String = CrashReportData::class.java.simpleName
- fun getLogcat(): String? {
- return getProcessOutput("logcat", "-b", "all", "-d")
+ private fun renderStackTrace(t: Throwable): String = try {
+ StringWriter().use { sw ->
+ PrintWriter(sw).use { pw -> t.printStackTrace(pw) }
+ sw.toString()
+ }
+ } catch (_: Throwable) {
+ "(stack trace unavailable)"
+ }
+
+ fun getLogcat(tailLines: Int? = null): String? = if (tailLines == null) {
+ getProcessOutput("logcat", "-b", "all", "-d")
+ } else {
+ getProcessOutput("logcat", "-b", "all", "-d", "-t", tailLines.toString())
}
fun getProcessOutput(vararg sArr: String?): String? {
diff --git a/app/src/main/java/com/onemoresecret/OmsFileProvider.kt b/app/src/main/java/com/onemoresecret/OmsFileProvider.kt
index c9d7653..66160ac 100644
--- a/app/src/main/java/com/onemoresecret/OmsFileProvider.kt
+++ b/app/src/main/java/com/onemoresecret/OmsFileProvider.kt
@@ -18,8 +18,13 @@ class OmsFileProvider : FileProvider() {
@JvmStatic
@Throws(IOException::class)
- fun create(ctx: Context, filename: String?, deleteOnExit: Boolean): FileRecord {
- val dir = File(ctx.cacheDir, "tmp")
+ fun create(
+ ctx: Context,
+ filename: String?,
+ deleteOnExit: Boolean,
+ subdir: String? = null
+ ): FileRecord {
+ val dir = if (subdir == null) File(ctx.cacheDir, "tmp") else File(ctx.cacheDir, "tmp/$subdir")
assert(
dir.exists() || dir.mkdirs() //otherwise something went wrong
)
diff --git a/app/src/main/java/com/onemoresecret/OmsUncaughtExceptionHandler.kt b/app/src/main/java/com/onemoresecret/OmsUncaughtExceptionHandler.kt
index 5aee64e..635680d 100644
--- a/app/src/main/java/com/onemoresecret/OmsUncaughtExceptionHandler.kt
+++ b/app/src/main/java/com/onemoresecret/OmsUncaughtExceptionHandler.kt
@@ -9,7 +9,7 @@ class OmsUncaughtExceptionHandler(private val activity: Activity) :
private val existingHandler = Thread.getDefaultUncaughtExceptionHandler()
override fun uncaughtException(t: Thread, e: Throwable) {
- val crashReportData = CrashReportData(e)
+ val crashReportData = CrashReportData(e, LOGCAT_TAIL_LINES)
val intent = Intent(activity.applicationContext, CrashReportActivity::class.java).apply{
putExtra(EXTRA_CRASH_REPORT, crashReportData)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -25,5 +25,6 @@ class OmsUncaughtExceptionHandler(private val activity: Activity) :
companion object {
const val EXTRA_CRASH_REPORT: String = "CRASH_REPORT"
+ private const val LOGCAT_TAIL_LINES = 2000
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index af384d9..e6d86af 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -81,6 +81,7 @@
Similar…
Encrypt With:
Share
+ Copy
Decrypted Message
Authentication was successful
Cannot apply layout filter - no layout selected
@@ -201,7 +202,6 @@
Ready to unlock oms4web
⚠️ this is a BETA version, see Release Notes
🚀 oms4web is here, give it a try!
- Could not send email
The report file has been attached. Please share the steps to reproduce the issue.
OneMoreSecret crash report
4096 bit RSA
diff --git a/app/src/standard/java/com/onemoresecret/qr/Analyzer.kt b/app/src/standard/java/com/onemoresecret/qr/Analyzer.kt
index 3acf408..f25d836 100644
--- a/app/src/standard/java/com/onemoresecret/qr/Analyzer.kt
+++ b/app/src/standard/java/com/onemoresecret/qr/Analyzer.kt
@@ -17,36 +17,46 @@ class Analyzer {
@OptIn(ExperimentalGetImage::class)
fun analyze(imageProxy: ImageProxy, onQRCodeFound: Consumer) {
- if (mlTask != null) return
+ if (mlTask != null) {
+ imageProxy.close()
+ return
+ }
val mediaImage = imageProxy.image
- if (mediaImage != null) {
- if (barcodeScanner == null) {
- barcodeScanner = BarcodeScanning.getClient(
- BarcodeScannerOptions.Builder()
- .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
- .build()
+ if (mediaImage == null) {
+ imageProxy.close()
+ } else {
+ try {
+ if (barcodeScanner == null) {
+ barcodeScanner = BarcodeScanning.getClient(
+ BarcodeScannerOptions.Builder()
+ .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
+ .build()
+ )
+ }
+ val inputImage = InputImage.fromMediaImage(
+ mediaImage,
+ imageProxy.imageInfo.rotationDegrees
)
- }
- val inputImage = InputImage.fromMediaImage(
- mediaImage,
- imageProxy.imageInfo.rotationDegrees
- )
- mlTask = barcodeScanner!!.process(inputImage)
- .addOnSuccessListener { barcodes ->
- mlTask = null
- imageProxy.close()
- for (barcode in barcodes) {
- onQRCodeFound.accept(barcode.rawValue)
+ mlTask = barcodeScanner!!.process(inputImage)
+ .addOnSuccessListener { barcodes ->
+ for (barcode in barcodes) {
+ onQRCodeFound.accept(barcode.rawValue)
+ }
}
- }
- .addOnFailureListener { e ->
- mlTask = null
- imageProxy.close()
- e.printStackTrace()
- }
+ .addOnFailureListener { e ->
+ e.printStackTrace()
+ }
+ .addOnCompleteListener {
+ mlTask = null
+ imageProxy.close()
+ }
+ } catch (t: Throwable) {
+ imageProxy.close()
+ throw t
+ }
}
}
}