Skip to content
Merged
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
28 changes: 28 additions & 0 deletions app/src/main/java/eu/darken/capod/App.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package eu.darken.capod

import android.app.Application
import android.os.Looper
import dagger.hilt.android.HiltAndroidApp
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.debug.Bugs
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
import eu.darken.capod.common.debug.logging.LogCatLogger
import eu.darken.capod.common.debug.logging.Logging
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.flow.throttleLatest
Expand All @@ -23,6 +27,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.system.exitProcess

@HiltAndroidApp
open class App : Application() {
Expand All @@ -37,6 +42,20 @@ open class App : Application() {
super.onCreate()
if (BuildConfig.DEBUG) Logging.install(LogCatLogger())

var foregroundExceptionHandled = false
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
if (throwable.isForegroundServiceTimingException() && !foregroundExceptionHandled) {
foregroundExceptionHandled = true
log(TAG, WARN) { "Suppressed foreground service timing exception: ${throwable.asLog()}" }
Bugs.report(tag = TAG, "Foreground service timing exception suppressed", exception = throwable)
Looper.loop()
return@setDefaultUncaughtExceptionHandler
}
log(TAG, ERROR) { "UNCAUGHT EXCEPTION: ${throwable.asLog()}" }
if (oldHandler != null) oldHandler.uncaughtException(thread, throwable) else exitProcess(1)
}

autoReporting.setup(this)

log(TAG) { "onCreate() done! ${Exception().asLog()}" }
Expand Down Expand Up @@ -64,5 +83,14 @@ open class App : Application() {

companion object {
internal val TAG = logTag("CAP")

private fun Throwable.isForegroundServiceTimingException(): Boolean {
var current: Throwable? = this
while (current != null) {
if (current.javaClass.simpleName == "ForegroundServiceDidNotStartInTimeException") return true
current = current.cause
}
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class MonitorService : Service() {
private var monitoringJob: Job? = null
@Volatile private var monitorGeneration = 0
private var foregroundStartFailed = false
private var injectionComplete = false

@SuppressLint("InlinedApi")
private fun promoteToForeground(notification: Notification): Boolean {
Expand Down Expand Up @@ -114,7 +115,12 @@ class MonitorService : Service() {
if (!promoteToForeground(MonitorNotifications.createEarlyNotification(this))) {
foregroundStartFailed = true
stopSelf()
super.onCreate()
try {
super.onCreate()
} catch (e: Exception) {
log(TAG, WARN) { "Hilt DI failed in onCreate() (foreground denied): ${e.asLog()}" }
Bugs.report(tag = TAG, "Hilt DI failed in onCreate() (foreground denied)", exception = e)
}
return
}

Expand All @@ -127,6 +133,7 @@ class MonitorService : Service() {
stopSelf()
return
}
injectionComplete = true
log(TAG, VERBOSE) { "onCreate()" }

// Replace early notification with the full one from injected MonitorNotifications.
Expand Down Expand Up @@ -302,12 +309,16 @@ class MonitorService : Service() {
log(TAG, VERBOSE) { "onDestroy()" }
monitorScope.cancel("Service destroyed")

if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
try {
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
} catch (e: Exception) {
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
if (injectionComplete) {
if (generalSettings.useExtraMonitorNotification.valueBlocking && !generalSettings.keepConnectedNotificationAfterDisconnect.valueBlocking) {
try {
notificationManager.cancel(MonitorNotifications.NOTIFICATION_ID_CONNECTED)
} catch (e: Exception) {
log(TAG, WARN) { "Failed to cancel connected notification: ${e.message}" }
}
}
} else {
log(TAG, WARN) { "onDestroy: Skipping notification cleanup, injection was incomplete." }
}

super.onDestroy()
Expand Down