diff --git a/app-common/src/main/java/eu/darken/capod/main/core/GeneralSettings.kt b/app-common/src/main/java/eu/darken/capod/main/core/GeneralSettings.kt index 881c5b8b..fa15c86a 100644 --- a/app-common/src/main/java/eu/darken/capod/main/core/GeneralSettings.kt +++ b/app-common/src/main/java/eu/darken/capod/main/core/GeneralSettings.kt @@ -24,6 +24,9 @@ class GeneralSettings @Inject constructor( override val preferences: SharedPreferences = context.getSharedPreferences("settings_general", Context.MODE_PRIVATE) val monitorMode = preferences.createFlowPreference("core.monitor.mode", MonitorMode.AUTOMATIC, moshi) + + val useExtraMonitorNotification = preferences.createFlowPreference("core.monitor.notification.connected", false) + val scannerMode = preferences.createFlowPreference("core.scanner.mode", ScannerMode.BALANCED, moshi) val showAll = preferences.createFlowPreference("core.showall.enabled", true) @@ -44,6 +47,7 @@ class GeneralSettings @Inject constructor( override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper( monitorMode, + useExtraMonitorNotification, scannerMode, showAll, minimumSignalQuality, diff --git a/app-common/src/main/res/drawable/ic_checkbox_blank_badge_24.xml b/app-common/src/main/res/drawable/ic_checkbox_blank_badge_24.xml new file mode 100644 index 00000000..f7f183c9 --- /dev/null +++ b/app-common/src/main/res/drawable/ic_checkbox_blank_badge_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt index 4598ccc4..4d259c1b 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorWorker.kt @@ -22,7 +22,6 @@ import eu.darken.capod.common.flow.throttleLatest import eu.darken.capod.main.core.GeneralSettings import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.main.core.PermissionTool -import eu.darken.capod.main.ui.widget.WidgetManager import eu.darken.capod.monitor.core.MonitorCoroutineScope import eu.darken.capod.monitor.core.PodMonitor import eu.darken.capod.monitor.ui.MonitorNotifications @@ -51,7 +50,7 @@ class MonitorWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val dispatcherProvider: DispatcherProvider, - private val monitorNotifications: MonitorNotifications, + private val notifications: MonitorNotifications, private val notificationManager: NotificationManager, private val generalSettings: GeneralSettings, private val permissionTool: PermissionTool, @@ -61,7 +60,6 @@ class MonitorWorker @AssistedInject constructor( private val autoConnect: AutoConnect, private val popUpReaction: PopUpReaction, private val popUpWindow: PopUpWindow, - private val widgetManager: WidgetManager, ) : CoroutineWorker(context, params) { private val workerScope = MonitorCoroutineScope() @@ -73,7 +71,7 @@ class MonitorWorker @AssistedInject constructor( } override suspend fun getForegroundInfo(): ForegroundInfo { - return monitorNotifications.getForegroundInfo(null) + return notifications.getForegroundInfo(null) } override suspend fun doWork(): Result = try { @@ -106,7 +104,7 @@ class MonitorWorker @AssistedInject constructor( return } - setForeground(monitorNotifications.getForegroundInfo(null)) + setForeground(notifications.getForegroundInfo(null)) val monitorJob = podMonitor.mainDevice .setupCommonEventHandlers(TAG) { "PodMonitor" } @@ -115,8 +113,14 @@ class MonitorWorker @AssistedInject constructor( .onEach { currentDevice -> notificationManager.notify( MonitorNotifications.NOTIFICATION_ID, - monitorNotifications.getNotification(currentDevice) + notifications.getNotification(currentDevice), ) + if (generalSettings.useExtraMonitorNotification.value && currentDevice != null) { + notificationManager.notify( + MonitorNotifications.NOTIFICATION_ID_CONNECTED, + notifications.getNotificationConnected(currentDevice), + ) + } } .catch { log(TAG, WARN) { "Pod Flow failed:\n${it.asLog()}" } @@ -128,20 +132,23 @@ class MonitorWorker @AssistedInject constructor( if (missingPermsFlow.isNotEmpty()) { log(TAG, WARN) { "Aborting, permissions are missing: $missingPermsFlow" } workerScope.coroutineContext.cancelChildren() - return@flatMapLatest emptyFlow() - } - combine( - generalSettings.monitorMode.flow, - generalSettings.mainDeviceAddress.flow, - bluetoothManager.connectedDevices(), - ) { monitorMode, mainAddress, connectedDevices -> - listOf(monitorMode, mainAddress, connectedDevices) + emptyFlow() + } else { + combine( + generalSettings.monitorMode.flow, + generalSettings.mainDeviceAddress.flow, + bluetoothManager.connectedDevices(), + ) { monitorMode, mainAddress, connectedDevices -> + listOf(monitorMode, mainAddress, connectedDevices) + } } } .setupCommonEventHandlers(TAG) { "MonitorMode" } .flatMapLatest { arguments -> val monitorMode = arguments[0] as MonitorMode val mainAddress = arguments[1] as String? + + @Suppress("UNCHECKED_CAST") val devices = arguments[2] as Collection log(TAG) { "Monitor mode: $monitorMode" } @@ -150,15 +157,18 @@ class MonitorWorker @AssistedInject constructor( // Cancel worker, ui scans manually workerScope.coroutineContext.cancelChildren() } + MonitorMode.ALWAYS -> emptyFlow() MonitorMode.AUTOMATIC -> flow { when { mainAddress == null && devices.isNotEmpty() -> { log(TAG, WARN) { "Main device address not set, staying alive while any is connected" } } + devices.any { it.address == mainAddress } -> { log(TAG) { "Main device is connected ($mainAddress), aborting any timeout." } } + else -> { log(TAG) { "No known Pods are connected, canceling worker soon." } delay(15 * 1000) diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt index a45a43dc..b373e7be 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt @@ -49,6 +49,11 @@ class MonitorNotifications @Inject constructor( context.getString(R.string.notification_channel_device_status_label), NotificationManager.IMPORTANCE_LOW ).run { notificationManager.createNotificationChannel(this) } + NotificationChannel( + NOTIFICATION_CHANNEL_ID_CONNECTED, + context.getString(R.string.notification_channel_device_status_connected_label), + NotificationManager.IMPORTANCE_LOW + ).run { notificationManager.createNotificationChannel(this) } val openIntent = Intent(context, MainActivity::class.java) val openPi = PendingIntent.getActivity( @@ -59,7 +64,6 @@ class MonitorNotifications @Inject constructor( ) builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply { - setChannelId(NOTIFICATION_CHANNEL_ID) setContentIntent(openPi) priority = NotificationCompat.PRIORITY_LOW setSmallIcon(eu.darken.capod.common.R.drawable.devic_earbuds_generic_both) @@ -145,11 +149,21 @@ class MonitorNotifications @Inject constructor( } suspend fun getNotification(podDevice: PodDevice?): Notification = builderLock.withLock { - getBuilder(podDevice).build() + getBuilder(podDevice).apply { + setChannelId(NOTIFICATION_CHANNEL_ID) + }.build() + } + + suspend fun getNotificationConnected(podDevice: PodDevice?): Notification = builderLock.withLock { + getBuilder(podDevice).apply { + setChannelId(NOTIFICATION_CHANNEL_ID_CONNECTED) + }.build() } suspend fun getForegroundInfo(podDevice: PodDevice?): ForegroundInfo = builderLock.withLock { - getBuilder(podDevice).toForegroundInfo() + getBuilder(podDevice).apply { + setChannelId(NOTIFICATION_CHANNEL_ID) + }.toForegroundInfo() } @SuppressLint("InlinedApi") @@ -168,8 +182,10 @@ class MonitorNotifications @Inject constructor( companion object { val TAG = logTag("Monitor", "Notifications") - private val NOTIFICATION_CHANNEL_ID = - "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status" + private val NOTIFICATION_CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status" + private val NOTIFICATION_CHANNEL_ID_CONNECTED = + "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status.connected" internal const val NOTIFICATION_ID = 1 + internal const val NOTIFICATION_ID_CONNECTED = 2 } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b41800bc..78c071c6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -51,6 +51,7 @@ Verbindungs-Popup anzeigen Zeigt ein Popup an, wenn das Gerät zum ersten Mal eine Verbindung herstellt. Geräte Status + Verbundenes Gerät Debug-Protokoll Zeichne alle Aktivitäten der App in einer Textdatei auf, die du weitergeben kannst. Größe @@ -77,7 +78,7 @@ Lizenzen Andere Einstellungen - Allgemeine Optimierungen, die die gesamte App betreffen. + Allgemeine Optimierungen, die die gesamte App betreffen. Danksagungen Automatische Fehlerberichte Meldet Probleme automatisch, z.B. Details zu einem App-Absturz, damit ich herausfinden kann, wie ich ihn beheben kann. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 70e444c9..f68d8af2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,8 @@ Monitor mode Under which circumstances this app monitors Bluetooth data. + Extra notification + Shows an extra notification when a device is connected. This lets you hide the permanent \"No devices\" notification by disabling the \"Device status\" channel. Scanner mode Should the Bluetooth Low Energy data scanner prioritize performance or conserve energy? Auto pause @@ -55,6 +57,7 @@ Show a popup when the device connects for the first time. Device status + Connected device Debug log Record everything the app is doing into a text file that you can share. diff --git a/app/src/main/res/xml/preferences_general.xml b/app/src/main/res/xml/preferences_general.xml index d8629c6c..3ff44fda 100644 --- a/app/src/main/res/xml/preferences_general.xml +++ b/app/src/main/res/xml/preferences_general.xml @@ -70,6 +70,13 @@ + + +