From c304a601488c845905fdccf862e23c7bb2853cf1 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Wed, 17 Apr 2024 17:09:46 -0700 Subject: [PATCH 1/3] Use a foreground service for reliable background transmitting --- .../bluetooth/ble/TransmitterManager.kt | 39 +++++++++++++++++++ .../common/sensors/BluetoothSensorManager.kt | 1 + .../common/sensors/SensorReceiverBase.kt | 6 +++ .../android/common/util/AppNotifChannels.kt | 4 +- common/src/main/res/values/strings.xml | 3 +- 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt index 64630c0e75d..bc749c38dc3 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt @@ -1,19 +1,34 @@ package io.homeassistant.companion.android.common.bluetooth.ble +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent import android.bluetooth.BluetoothManager import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseSettings import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService +import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.sensors.BluetoothSensorManager +import io.homeassistant.companion.android.common.sensors.SensorReceiverBase +import io.homeassistant.companion.android.common.sensors.SensorUpdateReceiver +import io.homeassistant.companion.android.common.util.CHANNEL_BLE_TRANSMITTER import java.util.UUID import org.altbeacon.beacon.Beacon +import org.altbeacon.beacon.BeaconManager import org.altbeacon.beacon.BeaconParser import org.altbeacon.beacon.BeaconTransmitter +import org.altbeacon.beacon.Identifier +import org.altbeacon.beacon.Region object TransmitterManager { private lateinit var physicalTransmitter: BeaconTransmitter + private lateinit var beaconManager: BeaconManager private lateinit var beacon: Beacon + private val region = Region("dummy-region", Identifier.parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"), null, null) private fun buildBeacon(haTransmitterI: IBeaconTransmitter): Beacon { val builder = Beacon.Builder() @@ -58,9 +73,31 @@ object TransmitterManager { } val bluetoothAdapter = context.getSystemService()?.adapter val bluetoothOn = bluetoothAdapter?.isEnabled == true + beaconManager = BeaconManager.getInstanceForApplication(context) + if (bluetoothOn) { val beacon = buildBeacon(haTransmitter) if (!physicalTransmitter.isStarted) { + val builder = NotificationCompat.Builder(context, CHANNEL_BLE_TRANSMITTER) + builder.setSmallIcon(R.drawable.ic_stat_ic_notification) + builder.setContentTitle(context.getString(R.string.beacon_transmitting)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel(CHANNEL_BLE_TRANSMITTER, context.getString(R.string.beacon_transmitting), NotificationManager.IMPORTANCE_LOW) + val notifManager = context.getSystemService()!! + notifManager.createNotificationChannel(channel) + } + val stopScanningIntent = Intent(context, SensorUpdateReceiver::class.java) + stopScanningIntent.action = SensorReceiverBase.ACTION_STOP_BEACON_TRANSMITTING + val stopScanningPendingIntent = PendingIntent.getBroadcast(context, 0, stopScanningIntent, PendingIntent.FLAG_MUTABLE) + builder.addAction(0, context.getString(R.string.disable), stopScanningPendingIntent) + beaconManager.enableForegroundServiceScanning(builder.build(), 445) + beaconManager.setEnableScheduledScanJobs(false) + beaconManager.beaconParsers.clear() + beaconManager.backgroundBetweenScanPeriod = Long.MAX_VALUE + beaconManager.backgroundScanPeriod = 0 + beaconManager.foregroundBetweenScanPeriod = Long.MAX_VALUE + beaconManager.foregroundScanPeriod = 0 + beaconManager.startMonitoring(region) physicalTransmitter.advertiseTxPowerLevel = getPowerLevel(haTransmitter) physicalTransmitter.advertiseMode = getAdvertiseMode(haTransmitter) physicalTransmitter.startAdvertising( @@ -115,5 +152,7 @@ object TransmitterManager { } haTransmitter.transmitting = false haTransmitter.state = "Stopped" + beaconManager.stopMonitoring(region) + beaconManager.disableForegroundServiceScanning() } } diff --git a/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt index 9de37828257..88ead78eb5f 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt @@ -114,6 +114,7 @@ class BluetoothSensorManager : SensorManager { } sensorDao.add(SensorSetting(bleTransmitter.id, SETTING_BLE_TRANSMIT_ENABLED, transmitEnabled.toString(), SensorSettingType.TOGGLE)) + SensorUpdateReceiver.updateSensors(context) } fun enableDisableBeaconMonitor(context: Context, monitorEnabled: Boolean) { diff --git a/common/src/main/java/io/homeassistant/companion/android/common/sensors/SensorReceiverBase.kt b/common/src/main/java/io/homeassistant/companion/android/common/sensors/SensorReceiverBase.kt index 3aeec8be617..276fa234d15 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/sensors/SensorReceiverBase.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/sensors/SensorReceiverBase.kt @@ -43,6 +43,7 @@ abstract class SensorReceiverBase : BroadcastReceiver() { const val ACTION_UPDATE_SENSOR = "io.homeassistant.companion.android.UPDATE_SENSOR" const val ACTION_UPDATE_SENSORS = "io.homeassistant.companion.android.UPDATE_SENSORS" const val ACTION_STOP_BEACON_SCANNING = "io.homeassistant.companion.android.STOP_BEACON_SCANNING" + const val ACTION_STOP_BEACON_TRANSMITTING = "io.homeassistant.companion.android.STOP_BEACON_TRANSMITTING" const val EXTRA_SENSOR_ID = "sensorId" fun shouldDoFastUpdates(context: Context): Boolean { @@ -111,6 +112,11 @@ abstract class SensorReceiverBase : BroadcastReceiver() { return } + if (intent.action == ACTION_STOP_BEACON_TRANSMITTING) { + BluetoothSensorManager.enableDisableBLETransmitter(context, false) + return + } + @Suppress("DEPRECATION") if (isSensorEnabled(LastUpdateManager.lastUpdate.id)) { LastUpdateManager().sendLastUpdate(context, intent.action) diff --git a/common/src/main/java/io/homeassistant/companion/android/common/util/AppNotifChannels.kt b/common/src/main/java/io/homeassistant/companion/android/common/util/AppNotifChannels.kt index cf37b19830b..38b00a4bc16 100755 --- a/common/src/main/java/io/homeassistant/companion/android/common/util/AppNotifChannels.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/util/AppNotifChannels.kt @@ -10,6 +10,7 @@ const val CHANNEL_LOCATION_DISABLED = "Location disabled" const val CHANNEL_DOWNLOADS = "downloads" const val CHANNEL_GENERAL = "general" const val CHANNEL_BEACON_MONITOR = "beacon" +const val CHANNEL_BLE_TRANSMITTER = "transmitter" val appCreatedChannels = listOf( CHANNEL_SENSOR_WORKER, @@ -21,5 +22,6 @@ val appCreatedChannels = listOf( CHANNEL_LOCATION_DISABLED, CHANNEL_DOWNLOADS, CHANNEL_GENERAL, - CHANNEL_BEACON_MONITOR + CHANNEL_BEACON_MONITOR, + CHANNEL_BLE_TRANSMITTER ) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 0f14ce276ee..36709dc8158 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -573,7 +573,7 @@ The current battery level of the device The current charging state of the battery The current battery temperature - Send BLE iBeacon with configured interval, used to track presence around house, e.g. together with roomassistant, esp32-mqtt-room or espresence projects.\n\nWarning: this can affect battery life, particularly if the \"Transmitter power\" setting is set to High or \"Advertise Mode\" is set to Low latency.\n\nSettings allow for specifying:\n- \"UUID\" (standard UUID format), \"Major\" and \"Minor\" (should be 0 - 65535), to tailor identifiers and groups. The default Minor value 40004 has the special meaning of forcing the beacon to be recognized by the iBeacon Tracker integration\n- \"Transmitter Power\" and \"Advertise Mode\" to help to preserve battery life (use lowest values if possible)\n - \"Measured Power\" to specify power measured at 1m (initial default -59)\n\nIt is also possible to set beacons to only be transmitted when connected to a Home Network WiFi SSID, which may be desirable for privacy and battery life.\n\nNote:\nAdditionally a separate setting exists (\"Enable Transmitter\") to stop or start transmitting. + Send BLE iBeacon with configured interval, used to track presence around house, e.g. together with roomassistant, esp32-mqtt-room or espresence projects. A notification will be shown on the device when transmitting.\n\nWarning: this can affect battery life, particularly if the \"Transmitter power\" setting is set to High or \"Advertise Mode\" is set to Low latency.\n\nSettings allow for specifying:\n- \"UUID\" (standard UUID format), \"Major\" and \"Minor\" (should be 0 - 65535), to tailor identifiers and groups. The default Minor value 40004 has the special meaning of forcing the beacon to be recognized by the iBeacon Tracker integration\n- \"Transmitter Power\" and \"Advertise Mode\" to help to preserve battery life (use lowest values if possible)\n - \"Measured Power\" to specify power measured at 1m (initial default -59)\n\nIt is also possible to set beacons to only be transmitted when connected to a Home Network WiFi SSID, which may be desirable for privacy and battery life.\n\nNote:\nAdditionally a separate setting exists (\"Enable Transmitter\") to stop or start transmitting. Scans for iBeacons and shows the IDs of nearby beacons and their distance in meters. A notification will be shown on the device when scanning is actively running.\n\nWarning: this can affect battery life, especially with a short \"Scan Interval\".\n\nSettings allow for specifying:\n- \"Filter Iterations\" (should be 1 - 100, default: 10), higher values will result in more stable measurements but also less responsiveness.\n- \"Filter RSSI Multiplier\" (should be 1.0 - 2.0, default: 1.05), can be used to archive more stable measurements when beacons are farther away. This will also affect responsiveness.\n- \"Scan Interval\" (default: 500) milliseconds between scans. Shorter intervals will drain the battery more quickly.\n- \"Scan Period\" (default: 1100) milliseconds to scan for beacons. Most beacons will send a signal every second so this value should be at least 1100ms.\n- \"UUID Filter\" allows to restrict the reported beacons by including (or excluding) those with the selected UUIDs.\n- \"Exclude selected UUIDs\", if false (default) only the beacons with the selected UUIDs are reported. If true all beacons except the selected ones are reported. Not available when \"UUID Filter\" is empty.\n\nNote:\nAdditionally a separate setting exists (\"Enable Beacon Monitor\") to stop or start scanning. Information about currently connected Bluetooth devices Whether Bluetooth is enabled on the device @@ -1261,4 +1261,5 @@ Tampering detected Update available Up-to-date + Beacon transmitting From 7e7a86f13bd6c17b6f59f7b7d71b025dd89175b8 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Thu, 18 Apr 2024 10:15:12 -0700 Subject: [PATCH 2/3] Clean up duplicate code and add proper scan permission --- .../bluetooth/ble/BeaconNotification.kt | 64 +++++++++++++++++++ .../common/bluetooth/ble/MonitoringManager.kt | 24 +------ .../bluetooth/ble/TransmitterManager.kt | 23 +------ .../common/sensors/BluetoothSensorManager.kt | 3 +- 4 files changed, 68 insertions(+), 46 deletions(-) create mode 100755 common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/BeaconNotification.kt diff --git a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/BeaconNotification.kt b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/BeaconNotification.kt new file mode 100755 index 00000000000..ab527deaa7d --- /dev/null +++ b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/BeaconNotification.kt @@ -0,0 +1,64 @@ +package io.homeassistant.companion.android.common.bluetooth.ble + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.content.getSystemService +import io.homeassistant.companion.android.common.R +import io.homeassistant.companion.android.common.sensors.SensorReceiverBase +import io.homeassistant.companion.android.common.sensors.SensorUpdateReceiver +import io.homeassistant.companion.android.common.util.CHANNEL_BEACON_MONITOR +import io.homeassistant.companion.android.common.util.CHANNEL_BLE_TRANSMITTER + +fun beaconNotification(isTransmitter: Boolean, context: Context): NotificationCompat.Builder { + val builder = NotificationCompat.Builder( + context, + if (isTransmitter) { + CHANNEL_BLE_TRANSMITTER + } else { + CHANNEL_BEACON_MONITOR + } + ) + builder.setSmallIcon(R.drawable.ic_stat_ic_notification) + builder.setContentTitle( + context.getString( + if (isTransmitter) { + R.string.beacon_transmitting + } else { + R.string.beacon_scanning + } + ) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + if (isTransmitter) { + CHANNEL_BLE_TRANSMITTER + } else { + CHANNEL_BEACON_MONITOR + }, + context.getString( + if (isTransmitter) { + R.string.beacon_transmitting + } else { + R.string.beacon_scanning + } + ), + NotificationManager.IMPORTANCE_LOW + ) + val notifManager = context.getSystemService()!! + notifManager.createNotificationChannel(channel) + } + val stopIntent = Intent(context, SensorUpdateReceiver::class.java) + stopIntent.action = if (isTransmitter) { + SensorReceiverBase.ACTION_STOP_BEACON_TRANSMITTING + } else { + SensorReceiverBase.ACTION_STOP_BEACON_SCANNING + } + val stopPendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_MUTABLE) + builder.addAction(0, context.getString(R.string.disable), stopPendingIntent) + return builder +} diff --git a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/MonitoringManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/MonitoringManager.kt index 0127322227c..9f399f0f153 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/MonitoringManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/MonitoringManager.kt @@ -1,18 +1,7 @@ package io.homeassistant.companion.android.common.bluetooth.ble -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.content.getSystemService import io.homeassistant.companion.android.common.BuildConfig -import io.homeassistant.companion.android.common.R -import io.homeassistant.companion.android.common.sensors.SensorReceiverBase -import io.homeassistant.companion.android.common.sensors.SensorUpdateReceiver -import io.homeassistant.companion.android.common.util.CHANNEL_BEACON_MONITOR import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -76,18 +65,7 @@ class MonitoringManager { } } - val builder = NotificationCompat.Builder(context, CHANNEL_BEACON_MONITOR) - builder.setSmallIcon(R.drawable.ic_stat_ic_notification) - builder.setContentTitle(context.getString(R.string.beacon_scanning)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel(CHANNEL_BEACON_MONITOR, context.getString(R.string.beacon_scanning), NotificationManager.IMPORTANCE_LOW) - val notifManager = context.getSystemService()!! - notifManager.createNotificationChannel(channel) - } - val stopScanningIntent = Intent(context, SensorUpdateReceiver::class.java) - stopScanningIntent.action = SensorReceiverBase.ACTION_STOP_BEACON_SCANNING - val stopScanningPendingIntent = PendingIntent.getBroadcast(context, 0, stopScanningIntent, PendingIntent.FLAG_MUTABLE) - builder.addAction(0, context.getString(R.string.disable), stopScanningPendingIntent) + val builder = beaconNotification(false, context) beaconManager.enableForegroundServiceScanning(builder.build(), 444) beaconManager.setEnableScheduledScanJobs(false) beaconManager.startRangingBeacons(region) diff --git a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt index bc749c38dc3..c972aba2a36 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt @@ -1,21 +1,11 @@ package io.homeassistant.companion.android.common.bluetooth.ble -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent import android.bluetooth.BluetoothManager import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseSettings import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat import androidx.core.content.getSystemService -import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.sensors.BluetoothSensorManager -import io.homeassistant.companion.android.common.sensors.SensorReceiverBase -import io.homeassistant.companion.android.common.sensors.SensorUpdateReceiver -import io.homeassistant.companion.android.common.util.CHANNEL_BLE_TRANSMITTER import java.util.UUID import org.altbeacon.beacon.Beacon import org.altbeacon.beacon.BeaconManager @@ -78,18 +68,7 @@ object TransmitterManager { if (bluetoothOn) { val beacon = buildBeacon(haTransmitter) if (!physicalTransmitter.isStarted) { - val builder = NotificationCompat.Builder(context, CHANNEL_BLE_TRANSMITTER) - builder.setSmallIcon(R.drawable.ic_stat_ic_notification) - builder.setContentTitle(context.getString(R.string.beacon_transmitting)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel(CHANNEL_BLE_TRANSMITTER, context.getString(R.string.beacon_transmitting), NotificationManager.IMPORTANCE_LOW) - val notifManager = context.getSystemService()!! - notifManager.createNotificationChannel(channel) - } - val stopScanningIntent = Intent(context, SensorUpdateReceiver::class.java) - stopScanningIntent.action = SensorReceiverBase.ACTION_STOP_BEACON_TRANSMITTING - val stopScanningPendingIntent = PendingIntent.getBroadcast(context, 0, stopScanningIntent, PendingIntent.FLAG_MUTABLE) - builder.addAction(0, context.getString(R.string.disable), stopScanningPendingIntent) + val builder = beaconNotification(true, context) beaconManager.enableForegroundServiceScanning(builder.build(), 445) beaconManager.setEnableScheduledScanJobs(false) beaconManager.beaconParsers.clear() diff --git a/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt index 88ead78eb5f..cc33b3f39e8 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/sensors/BluetoothSensorManager.kt @@ -151,7 +151,8 @@ class BluetoothSensorManager : SensorManager { (sensorId == bleTransmitter.id && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> { arrayOf( Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_CONNECT + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN ) } (sensorId == beaconMonitor.id && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) -> { From bfe0c9cc0264eb76ec1cc8e522c1bfdc6acdafc1 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 19 Apr 2024 07:45:59 -0700 Subject: [PATCH 3/3] Handle uninitialized property --- .../bluetooth/ble/TransmitterManager.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt index c972aba2a36..02f614fc499 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/bluetooth/ble/TransmitterManager.kt @@ -16,7 +16,7 @@ import org.altbeacon.beacon.Region object TransmitterManager { private lateinit var physicalTransmitter: BeaconTransmitter - private lateinit var beaconManager: BeaconManager + private var beaconManager: BeaconManager? = null private lateinit var beacon: Beacon private val region = Region("dummy-region", Identifier.parse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"), null, null) @@ -69,14 +69,14 @@ object TransmitterManager { val beacon = buildBeacon(haTransmitter) if (!physicalTransmitter.isStarted) { val builder = beaconNotification(true, context) - beaconManager.enableForegroundServiceScanning(builder.build(), 445) - beaconManager.setEnableScheduledScanJobs(false) - beaconManager.beaconParsers.clear() - beaconManager.backgroundBetweenScanPeriod = Long.MAX_VALUE - beaconManager.backgroundScanPeriod = 0 - beaconManager.foregroundBetweenScanPeriod = Long.MAX_VALUE - beaconManager.foregroundScanPeriod = 0 - beaconManager.startMonitoring(region) + beaconManager?.enableForegroundServiceScanning(builder.build(), 445) + beaconManager?.setEnableScheduledScanJobs(false) + beaconManager?.beaconParsers?.clear() + beaconManager?.backgroundBetweenScanPeriod = Long.MAX_VALUE + beaconManager?.backgroundScanPeriod = 0 + beaconManager?.foregroundBetweenScanPeriod = Long.MAX_VALUE + beaconManager?.foregroundScanPeriod = 0 + beaconManager?.startMonitoring(region) physicalTransmitter.advertiseTxPowerLevel = getPowerLevel(haTransmitter) physicalTransmitter.advertiseMode = getAdvertiseMode(haTransmitter) physicalTransmitter.startAdvertising( @@ -131,7 +131,7 @@ object TransmitterManager { } haTransmitter.transmitting = false haTransmitter.state = "Stopped" - beaconManager.stopMonitoring(region) - beaconManager.disableForegroundServiceScanning() + beaconManager?.stopMonitoring(region) + beaconManager?.disableForegroundServiceScanning() } }