From cfed733fa7e9fdc4652d671e31d7d67f1bfca00f Mon Sep 17 00:00:00 2001 From: darken Date: Mon, 9 Feb 2026 09:13:27 +0100 Subject: [PATCH] fix: Guard MonitorService start against missing Bluetooth permissions --- .claude/rules/architecture.md | 9 ++++----- .../capod/monitor/core/worker/MonitorControl.kt | 11 +++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 9c22bfbf..0a21fd5b 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -25,8 +25,8 @@ globs: ### PodMonitor System - `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE -- `MonitorControl`: Manages background monitoring worker lifecycle -- `MonitorWorker`: Background worker that continuously scans for AirPods +- `MonitorControl`: Manages MonitorService lifecycle +- `MonitorService`: Foreground service that continuously scans for AirPods - `BluetoothEventReceiver`: Handles system Bluetooth events ### Reaction System @@ -57,7 +57,7 @@ globs: The app follows a unidirectional data flow: 1. `BluetoothEventReceiver` detects Bluetooth events -2. `MonitorWorker` scans for AirPods beacon data +2. `MonitorService` scans for AirPods beacon data 3. `PodMonitor` processes and stores device information 4. ViewModels observe monitor data via repositories 5. UI components react to ViewModel state changes @@ -65,7 +65,7 @@ The app follows a unidirectional data flow: ## Bluetooth LE Implementation -The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in `MonitorWorker` which runs as a long-lived background task. +The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in `MonitorService` which runs as a foreground service. ## Multi-Platform Considerations @@ -80,6 +80,5 @@ Code shared between phone and Wear OS apps is placed in `app-common`. When modif - **Hilt**: Dependency injection framework - **AndroidX Navigation**: Fragment navigation with SafeArgs -- **WorkManager**: Background task scheduling for monitoring - **Moshi**: JSON serialization for configuration and debugging - **Material Design**: UI components following Material Design guidelines diff --git a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt index f5ea4536..6af803f5 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/worker/MonitorControl.kt @@ -6,6 +6,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE 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.permissions.Permission import eu.darken.capod.common.startServiceCompat import javax.inject.Inject import javax.inject.Singleton @@ -19,11 +20,21 @@ class MonitorControl @Inject constructor( forceStart: Boolean = false, ) { log(TAG, VERBOSE) { "startMonitor(forceStart=$forceStart)" } + + val hasBluetoothPermission = + Permission.BLUETOOTH.isGranted(context) || Permission.BLUETOOTH_CONNECT.isGranted(context) + if (!hasBluetoothPermission) { + log(TAG, WARN) { "Missing Bluetooth permission, not starting monitor service." } + return + } + try { context.startServiceCompat(MonitorService.intent(context, forceStart)) log(TAG) { "Monitor start request sent." } } catch (e: IllegalStateException) { log(TAG, WARN) { "Failed to start monitor service: ${e.message}" } + } catch (e: SecurityException) { + log(TAG, WARN) { "Failed to start monitor service, permission issue: ${e.message}" } } }