diff --git a/app/src/full/java/io/homeassistant/companion/android/CrashHandling.kt b/app/src/full/java/io/homeassistant/companion/android/CrashHandling.kt index afcece3ffe9..2f72f334a9a 100644 --- a/app/src/full/java/io/homeassistant/companion/android/CrashHandling.kt +++ b/app/src/full/java/io/homeassistant/companion/android/CrashHandling.kt @@ -1,25 +1,14 @@ package io.homeassistant.companion.android import android.content.Context -import android.util.Log -import io.sentry.SentryOptions import io.sentry.android.core.SentryAndroid -import java.io.File -import java.io.PrintWriter import java.net.ConnectException import java.net.SocketTimeoutException import java.net.UnknownHostException -import java.text.SimpleDateFormat -import java.util.Locale -import java.util.concurrent.TimeUnit import javax.net.ssl.SSLException import javax.net.ssl.SSLHandshakeException import javax.net.ssl.SSLPeerUnverifiedException import javax.net.ssl.SSLProtocolException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -private const val FATAL_CRASH_FILE = "/fatalcrash/last_crash" fun initCrashReporting(context: Context, enabled: Boolean) { // Don't init on debug builds or when disabled @@ -31,27 +20,6 @@ fun initCrashReporting(context: Context, enabled: Boolean) { options.isEnableAutoSessionTracking = true options.isEnableNdk = false - options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> - if (event.isCrashed && event.throwable != null) { - try { - val crashFile = File(context.applicationContext.cacheDir.absolutePath + FATAL_CRASH_FILE) - if (!crashFile.exists()) { - crashFile.parentFile?.mkdirs() - crashFile.createNewFile() - } - val stacktraceWriter = PrintWriter(crashFile) - val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(event.timestamp) - stacktraceWriter.print("$timestamp: ") - event.throwable?.printStackTrace(stacktraceWriter) - stacktraceWriter.close() - } catch (e: Exception) { - Log.i("CrashHandling", "Tried saving fatal crash but encountered exception", e) - } - } - - return@BeforeSendCallback event - } - val ignoredEvents = arrayOf( ConnectException::class.java, SocketTimeoutException::class.java, @@ -66,23 +34,4 @@ fun initCrashReporting(context: Context, enabled: Boolean) { } } -suspend fun getLatestFatalCrash(context: Context, enabled: Boolean): String? = withContext(Dispatchers.IO) { - if (!shouldEnableCrashHandling(enabled)) { - return@withContext null - } - - var toReturn: String? = null - try { - val crashFile = File(context.applicationContext.cacheDir.absolutePath + FATAL_CRASH_FILE) - if (crashFile.exists() && - crashFile.lastModified() >= (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12)) - ) { // Existing, recent file - toReturn = crashFile.readText().trim().ifBlank { null } - } - } catch (e: Exception) { - Log.e("CrashHandling", "Encountered exception while reading crash log file", e) - } - return@withContext toReturn -} - private fun shouldEnableCrashHandling(enabled: Boolean) = !BuildConfig.DEBUG && enabled diff --git a/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt b/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt index 5c0bc6f63eb..5cc68001172 100644 --- a/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt +++ b/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt @@ -23,6 +23,7 @@ import io.homeassistant.companion.android.database.settings.SensorUpdateFrequenc import io.homeassistant.companion.android.sensors.SensorReceiver import io.homeassistant.companion.android.settings.language.LanguagesManager import io.homeassistant.companion.android.util.LifecycleHandler +import io.homeassistant.companion.android.util.initCrashSaving import io.homeassistant.companion.android.websocket.WebsocketBroadcastReceiver import io.homeassistant.companion.android.widgets.button.ButtonWidget import io.homeassistant.companion.android.widgets.entity.EntityWidget @@ -60,6 +61,7 @@ open class HomeAssistantApplication : Application() { applicationContext, prefsRepository.isCrashReporting() ) + initCrashSaving(applicationContext) } languagesManager.applyCurrentLang() diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/log/LogFragment.kt b/app/src/main/java/io/homeassistant/companion/android/settings/log/LogFragment.kt index 270230eaf62..d694f3b3080 100644 --- a/app/src/main/java/io/homeassistant/companion/android/settings/log/LogFragment.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/log/LogFragment.kt @@ -31,8 +31,8 @@ import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.R import io.homeassistant.companion.android.common.R as commonR import io.homeassistant.companion.android.common.data.prefs.PrefsRepository -import io.homeassistant.companion.android.getLatestFatalCrash import io.homeassistant.companion.android.util.LogcatReader +import io.homeassistant.companion.android.util.getLatestFatalCrash import java.io.File import java.util.Calendar import javax.inject.Inject @@ -115,7 +115,7 @@ class LogFragment : Fragment() { // Runs with Dispatcher IO processLog = LogcatReader.readLog() - crashLog = getLatestFatalCrash(requireContext(), prefsRepository.isCrashReporting()) + crashLog = getLatestFatalCrash(requireContext()) showLog() showHideLogLoader(false) diff --git a/app/src/main/java/io/homeassistant/companion/android/util/CrashSaving.kt b/app/src/main/java/io/homeassistant/companion/android/util/CrashSaving.kt new file mode 100644 index 00000000000..91ffa662577 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/util/CrashSaving.kt @@ -0,0 +1,54 @@ +package io.homeassistant.companion.android.util + +import android.content.Context +import android.util.Log +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +private const val FATAL_CRASH_FILE = "/fatalcrash/last_crash" + +fun initCrashSaving(context: Context) { + val handler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler { thread, exception -> + // Try saving the crash in a file + try { + val crashFile = File(context.applicationContext.cacheDir.absolutePath + FATAL_CRASH_FILE) + if (!crashFile.exists()) { + crashFile.parentFile?.mkdirs() + crashFile.createNewFile() + } + + crashFile.writeText( + """|Timestamp: ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())} + |Thread: ${thread.name} + |Exception: ${exception.stackTraceToString()} + """.trimMargin() + ) + } catch (e: Exception) { + Log.i("CrashSaving", "Tried saving fatal crash but encountered exception", e) + } + + // Send to crash handling and/or system (and crash) + handler?.uncaughtException(thread, exception) + } +} + +suspend fun getLatestFatalCrash(context: Context): String? = withContext(Dispatchers.IO) { + var toReturn: String? = null + try { + val crashFile = File(context.applicationContext.cacheDir.absolutePath + FATAL_CRASH_FILE) + if (crashFile.exists() && + crashFile.lastModified() >= (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12)) + ) { // Existing, recent file + toReturn = crashFile.readText().trim().ifBlank { null } + } + } catch (e: Exception) { + Log.e("CrashSaving", "Encountered exception while reading crash log file", e) + } + return@withContext toReturn +} diff --git a/app/src/minimal/java/io/homeassistant/companion/android/CrashHandling.kt b/app/src/minimal/java/io/homeassistant/companion/android/CrashHandling.kt index 27bda7ef631..fe5a5483bbd 100644 --- a/app/src/minimal/java/io/homeassistant/companion/android/CrashHandling.kt +++ b/app/src/minimal/java/io/homeassistant/companion/android/CrashHandling.kt @@ -1,14 +1,7 @@ package io.homeassistant.companion.android import android.content.Context -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext fun initCrashReporting(context: Context, enabled: Boolean) { // Noop } - -suspend fun getLatestFatalCrash(context: Context, enabled: Boolean): String? = withContext(Dispatchers.IO) { - // Noop - return@withContext null -}