Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a foreground service for reliable background transmitting #4347

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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<NotificationManager>()!!
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
}
@@ -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
Expand Down Expand Up @@ -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<NotificationManager>()!!
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)
Expand Down
Expand Up @@ -8,12 +8,17 @@ import androidx.core.content.getSystemService
import io.homeassistant.companion.android.common.sensors.BluetoothSensorManager
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 var beaconManager: BeaconManager? = null
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()
Expand Down Expand Up @@ -58,9 +63,20 @@ object TransmitterManager {
}
val bluetoothAdapter = context.getSystemService<BluetoothManager>()?.adapter
val bluetoothOn = bluetoothAdapter?.isEnabled == true
beaconManager = BeaconManager.getInstanceForApplication(context)

if (bluetoothOn) {
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)
physicalTransmitter.advertiseTxPowerLevel = getPowerLevel(haTransmitter)
physicalTransmitter.advertiseMode = getAdvertiseMode(haTransmitter)
physicalTransmitter.startAdvertising(
Expand Down Expand Up @@ -115,5 +131,7 @@ object TransmitterManager {
}
haTransmitter.transmitting = false
haTransmitter.state = "Stopped"
beaconManager?.stopMonitoring(region)
beaconManager?.disableForegroundServiceScanning()
}
}
Expand Up @@ -112,6 +112,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) {
Expand Down Expand Up @@ -148,7 +149,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) -> {
Expand Down
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Expand Up @@ -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,
Expand All @@ -21,5 +22,6 @@ val appCreatedChannels = listOf(
CHANNEL_LOCATION_DISABLED,
CHANNEL_DOWNLOADS,
CHANNEL_GENERAL,
CHANNEL_BEACON_MONITOR
CHANNEL_BEACON_MONITOR,
CHANNEL_BLE_TRANSMITTER
)
3 changes: 2 additions & 1 deletion common/src/main/res/values/strings.xml
Expand Up @@ -573,7 +573,7 @@
<string name="sensor_description_battery_level">The current battery level of the device</string>
<string name="sensor_description_battery_state">The current charging state of the battery</string>
<string name="sensor_description_battery_temperature">The current battery temperature</string>
<string name="sensor_description_bluetooth_ble_emitter">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.</string>
<string name="sensor_description_bluetooth_ble_emitter">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.</string>
<string name="sensor_description_bluetooth_ble_beacon_monitor">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.</string>
<string name="sensor_description_bluetooth_connection">Information about currently connected Bluetooth devices</string>
<string name="sensor_description_bluetooth_state">Whether Bluetooth is enabled on the device</string>
Expand Down Expand Up @@ -1232,4 +1232,5 @@
<string name="state_sunny">Sunny</string>
<string name="state_windy">Windy</string>
<string name="feedback">Feedback</string>
<string name="beacon_transmitting">Beacon transmitting</string>
</resources>