Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 72 additions & 57 deletions app/src/main/java/com/onemoresecret/CrashReportActivity.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
)
}
}
}
Expand Down Expand Up @@ -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
}
}

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))
}
24 changes: 18 additions & 6 deletions app/src/main/java/com/onemoresecret/CrashReportData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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? {
Expand Down
9 changes: 7 additions & 2 deletions app/src/main/java/com/onemoresecret/OmsFileProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<string name="similar">Similar…</string>
<string name="encrypt_with">Encrypt With:</string>
<string name="share">Share</string>
<string name="copy">Copy</string>
<string name="decrypted_message">Decrypted Message</string>
<string name="auth_successful">Authentication was successful</string>
<string name="cannot_apply_layout_filter">Cannot apply layout filter - no layout selected</string>
Expand Down Expand Up @@ -201,7 +202,6 @@
<string name="ready_to_unlock_oms4web">Ready to unlock oms4web</string>
<string name="qr_banner">⚠️ this is a BETA version, see <a href="https://github.com/stud0709/OneMoreSecret/releases">Release Notes</a></string>
<string name="qr_banner1">🚀 oms4web is <a href="https://stud0709.github.io/oms4web/">here</a>, give it a try!</string>
<string name="could_not_send_email">Could not send email</string>
<string name="crash_email_body">The report file has been attached. Please share the steps to reproduce the issue.</string>
<string name="crash_email_subject">OneMoreSecret crash report</string>
<string name="rsa_key_length_4096_bit">4096 bit RSA</string>
Expand Down
58 changes: 34 additions & 24 deletions app/src/standard/java/com/onemoresecret/qr/Analyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,46 @@ class Analyzer {

@OptIn(ExperimentalGetImage::class)
fun analyze(imageProxy: ImageProxy, onQRCodeFound: Consumer<String?>) {
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
}
}
}
}