Add Wear OS battery sensors (#1890)
* Move database to common module * Fix lint * Better kapt error reporting * Alias common module's resources into app * Move base sensor receiver & worker to common module * Add sensor receiver & worker to wear module * Schedule sensors Worker on app start up * Export the SensorReceiver in the wear manifest * Upgrade base and wear sensors Receivers and Workers to Hilt + hoist updateSensors to sensors Receiver base + introduce base location manager + fix minimal sensor stubs * Re-add important battery sensor info * Remove unused imports * Listen for battery updates in wear app * Listen for screen updates in wear app * Fix ktlint * Add sensors to wear OS on home screen * Stop listening to screens and power save mode The sensor isn't added yet * Remove commented code due to absent sensors... for now. Mwahahaha! * Remove unused import
|
@ -27,12 +27,6 @@ android {
|
|||
versionCode = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1
|
||||
|
||||
manifestPlaceholders["sentryRelease"] = "$applicationId@$versionName"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments(mapOf("room.incremental" to "true"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
|
@ -119,6 +113,10 @@ android {
|
|||
isAbortOnError = false
|
||||
disable("MissingTranslation")
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
}
|
||||
|
||||
play {
|
||||
|
@ -160,10 +158,6 @@ dependencies {
|
|||
implementation("androidx.wear:wear-remote-interactions:1.0.0")
|
||||
implementation("com.google.android.gms:play-services-wearable:17.1.0")
|
||||
|
||||
implementation("androidx.room:room-runtime:2.3.0")
|
||||
implementation("androidx.room:room-ktx:2.3.0")
|
||||
kapt("androidx.room:room-compiler:2.3.0")
|
||||
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.9.3")
|
||||
implementation("com.squareup.picasso:picasso:2.8")
|
||||
|
@ -175,7 +169,6 @@ dependencies {
|
|||
"fullImplementation"("io.sentry:sentry-android:5.4.1")
|
||||
"fullImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2")
|
||||
|
||||
implementation("androidx.work:work-runtime-ktx:2.7.0")
|
||||
implementation("androidx.biometric:biometric:1.1.0")
|
||||
implementation("androidx.webkit:webkit:1.4.0")
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.google.android.gms.location.SleepSegmentEvent
|
|||
import com.google.android.gms.location.SleepSegmentRequest
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ActivitySensorManager : BroadcastReceiver(), SensorManager {
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.os.Build
|
|||
import android.util.Log
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import io.homeassistant.companion.android.location.HighAccuracyLocationService
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.homeassistant.companion.android.sensors
|
|||
|
||||
import android.Manifest
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
|
@ -24,9 +23,10 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.bluetooth.BluetoothUtils
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.UpdateLocation
|
||||
import io.homeassistant.companion.android.common.data.integration.ZoneAttributes
|
||||
import io.homeassistant.companion.android.common.sensors.LocationSensorManagerBase
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Attribute
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
|
@ -36,10 +36,9 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||
class LocationSensorManager : LocationSensorManagerBase() {
|
||||
|
||||
companion object {
|
||||
private const val SETTING_ACCURACY = "location_minimum_accuracy"
|
||||
|
@ -126,9 +125,6 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
|||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
lateinit var latestContext: Context
|
||||
|
|
|
@ -12,8 +12,8 @@ import android.os.PowerManager
|
|||
import android.telephony.TelephonyManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
|
||||
import io.homeassistant.companion.android.common.sensors.LastUpdateManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.sensors.LastUpdateManager
|
||||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
import io.homeassistant.companion.android.widgets.button.ButtonWidget
|
||||
import io.homeassistant.companion.android.widgets.entity.EntityWidget
|
||||
|
|
|
@ -75,8 +75,9 @@ abstract class TileExtensions : TileService() {
|
|||
tile.state = if (state.state == "on" || state.state == "open") Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
} else
|
||||
tile.state = Tile.STATE_INACTIVE
|
||||
if (tileData.iconId != null) {
|
||||
val icon = getTileIcon(tileData.iconId, context)
|
||||
val iconId = tileData.iconId
|
||||
if (iconId != null) {
|
||||
val icon = getTileIcon(iconId, context)
|
||||
tile.icon = Icon.createWithBitmap(icon)
|
||||
}
|
||||
tile.updateTile()
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.util.Log
|
|||
import androidx.annotation.RequiresApi
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import java.math.RoundingMode
|
||||
|
||||
class AppSensorManager : SensorManager {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.media.AudioDeviceInfo
|
|||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class AudioSensorManager : SensorManager {
|
||||
companion object {
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.homeassistant.companion.android.R
|
|||
import io.homeassistant.companion.android.bluetooth.BluetoothUtils
|
||||
import io.homeassistant.companion.android.bluetooth.ble.IBeaconTransmitter
|
||||
import io.homeassistant.companion.android.bluetooth.ble.TransmitterManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import java.util.UUID
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.provider.Settings.Global
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class DNDSensorManager : SensorManager {
|
||||
companion object {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class KeyguardSensorManager : SensorManager {
|
||||
companion object {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import java.text.SimpleDateFormat
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.hardware.SensorEventListener
|
|||
import android.hardware.SensorManager.SENSOR_DELAY_NORMAL
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class LightSensorManager : SensorManager, SensorEventListener {
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.provider.Settings
|
|||
import android.provider.Settings.Global.getInt
|
||||
import android.telephony.TelephonyManager
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class MobileDataManager : SensorManager {
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.net.wifi.WifiManager
|
|||
import android.os.Build
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import okhttp3.Call
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.AlarmManager
|
|||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
import java.text.SimpleDateFormat
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.service.notification.StatusBarNotification
|
|||
import android.util.Log
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class NotificationSensorManager : NotificationListenerService(), SensorManager {
|
||||
companion object {
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.telephony.SubscriptionInfo
|
|||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyManager
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class PhoneStateSensorManager : SensorManager {
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
|||
import android.os.PowerManager
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class PowerSensorManager : SensorManager {
|
||||
companion object {
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.hardware.SensorEventListener
|
|||
import android.hardware.SensorManager.SENSOR_DELAY_NORMAL
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import java.math.RoundingMode
|
||||
|
||||
class PressureSensorManager : SensorManager, SensorEventListener {
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.hardware.SensorEventListener
|
|||
import android.hardware.SensorManager.SENSOR_DELAY_NORMAL
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ProximitySensorManager : SensorManager, SensorEventListener {
|
||||
|
|
|
@ -23,6 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.bluetooth.BluetoothUtils
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Sensor
|
||||
import io.homeassistant.companion.android.database.sensor.SensorDao
|
||||
|
|
|
@ -3,28 +3,27 @@ package io.homeassistant.companion.android.sensors
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.media.AudioManager
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.SensorRegistration
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.sensors.BatterySensorManager
|
||||
import io.homeassistant.companion.android.common.sensors.LastUpdateManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorReceiverBase
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SensorReceiver : BroadcastReceiver() {
|
||||
class SensorReceiver : SensorReceiverBase() {
|
||||
|
||||
override val tag: String
|
||||
get() = TAG
|
||||
|
||||
override val currentAppVersion: String
|
||||
get() = BuildConfig.VERSION_NAME
|
||||
|
||||
override val managers: List<SensorManager>
|
||||
get() = MANAGERS
|
||||
|
||||
companion object {
|
||||
const val TAG = "SensorReceiver"
|
||||
|
@ -59,21 +58,9 @@ class SensorReceiver : BroadcastReceiver() {
|
|||
"io.homeassistant.companion.android.background.REQUEST_SENSORS_UPDATE"
|
||||
}
|
||||
|
||||
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
private val chargingActions = listOf(
|
||||
Intent.ACTION_BATTERY_LOW,
|
||||
Intent.ACTION_BATTERY_OKAY,
|
||||
Intent.ACTION_POWER_CONNECTED,
|
||||
Intent.ACTION_POWER_DISCONNECTED
|
||||
)
|
||||
|
||||
// Suppress Lint because we only register for the receiver if the android version matches the intent
|
||||
@SuppressLint("InlinedApi")
|
||||
private val skippableActions = mapOf(
|
||||
override val skippableActions = mapOf(
|
||||
"android.app.action.NEXT_ALARM_CLOCK_CHANGED" to NextAlarmManager.nextAlarm.id,
|
||||
"android.bluetooth.device.action.ACL_CONNECTED" to BluetoothSensorManager.bluetoothConnection.id,
|
||||
"android.bluetooth.device.action.ACL_DISCONNECTED" to BluetoothSensorManager.bluetoothConnection.id,
|
||||
|
@ -87,142 +74,4 @@ class SensorReceiver : BroadcastReceiver() {
|
|||
AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED to AudioSensorManager.speakerphoneState.id,
|
||||
AudioManager.RINGER_MODE_CHANGED_ACTION to AudioSensorManager.audioSensor.id
|
||||
)
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
||||
if (skippableActions.containsKey(intent.action)) {
|
||||
val sensor = skippableActions[intent.action]
|
||||
if (!isSensorEnabled(context, sensor!!)) {
|
||||
Log.d(
|
||||
TAG,
|
||||
String.format
|
||||
("Sensor %s corresponding to received event %s is disabled, skipping sensors update", sensor, intent.action)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (isSensorEnabled(context, LastUpdateManager.lastUpdate.id)) {
|
||||
LastUpdateManager().sendLastUpdate(context, intent.action)
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val allSettings = sensorDao.getSettings(LastUpdateManager.lastUpdate.id)
|
||||
for (setting in allSettings) {
|
||||
if (setting.value != "" && intent.action == setting.value) {
|
||||
val eventData = intent.extras?.keySet()?.map { it.toString() to intent.extras?.get(it).toString() }?.toMap()?.plus("intent" to intent.action.toString())
|
||||
?: mapOf("intent" to intent.action.toString())
|
||||
Log.d(TAG, "Event data: $eventData")
|
||||
ioScope.launch {
|
||||
try {
|
||||
integrationUseCase.fireEvent(
|
||||
"android.intent_received",
|
||||
eventData as Map<String, Any>
|
||||
)
|
||||
Log.d(TAG, "Event successfully sent to Home Assistant")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to send event data to Home Assistant", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ioScope.launch {
|
||||
updateSensors(context, integrationUseCase)
|
||||
if (chargingActions.contains(intent.action)) {
|
||||
// Add a 5 second delay to perform another update so charging state updates completely.
|
||||
// This is necessary as the system needs a few seconds to verify the charger.
|
||||
delay(5000L)
|
||||
updateSensors(context, integrationUseCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSensorEnabled(context: Context, id: String): Boolean {
|
||||
return AppDatabase.getInstance(context).sensorDao().get(id)?.enabled == true
|
||||
}
|
||||
|
||||
suspend fun updateSensors(
|
||||
context: Context,
|
||||
integrationUseCase: IntegrationRepository
|
||||
) {
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val enabledRegistrations = mutableListOf<SensorRegistration<Any>>()
|
||||
|
||||
val checkDeviceRegistration = integrationUseCase.getRegistration()
|
||||
if (checkDeviceRegistration.appVersion == null) {
|
||||
Log.w(TAG, "Device not registered, skipping sensor update/registration")
|
||||
return
|
||||
}
|
||||
|
||||
val currentAppVersion = BuildConfig.VERSION_NAME
|
||||
val currentHaVersion = integrationUseCase.getHomeAssistantVersion()
|
||||
|
||||
MANAGERS.forEach { manager ->
|
||||
|
||||
// Since we don't have this manager injected it doesn't fulfil it's injects, manually
|
||||
// inject for now I guess?
|
||||
if (manager is LocationSensorManager)
|
||||
manager.integrationUseCase = integrationUseCase
|
||||
|
||||
try {
|
||||
manager.requestSensorUpdate(context)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Issue requesting updates for ${context.getString(manager.name)}", e)
|
||||
}
|
||||
manager.getAvailableSensors(context).forEach { basicSensor ->
|
||||
val fullSensor = sensorDao.getFull(basicSensor.id)
|
||||
val sensor = fullSensor?.sensor
|
||||
val sensorCoreRegistration = sensor?.coreRegistration
|
||||
val sensorAppRegistration = sensor?.appRegistration
|
||||
|
||||
// Always register enabled sensors in case of available entity updates
|
||||
// when app or core version change is detected every 4 hours
|
||||
if (sensor?.enabled == true && sensor.type.isNotBlank() && sensor.icon.isNotBlank() &&
|
||||
(currentAppVersion != sensorAppRegistration || currentHaVersion != sensorCoreRegistration || !sensor.registered)
|
||||
) {
|
||||
val reg = fullSensor.toSensorRegistration()
|
||||
val config = Configuration(context.resources.configuration)
|
||||
config.setLocale(Locale("en"))
|
||||
reg.name = context.createConfigurationContext(config).resources.getString(basicSensor.name)
|
||||
|
||||
try {
|
||||
integrationUseCase.registerSensor(reg)
|
||||
sensor.registered = true
|
||||
sensor.coreRegistration = currentHaVersion
|
||||
sensor.appRegistration = currentAppVersion
|
||||
sensorDao.update(sensor)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Issue registering sensor: ${reg.uniqueId}", e)
|
||||
}
|
||||
}
|
||||
if (sensor?.enabled == true && sensor.registered && sensor.state != sensor.lastSentState) {
|
||||
enabledRegistrations.add(fullSensor.toSensorRegistration())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledRegistrations.isNotEmpty()) {
|
||||
var success = false
|
||||
try {
|
||||
success = integrationUseCase.updateSensors(enabledRegistrations.toTypedArray())
|
||||
enabledRegistrations.forEach {
|
||||
sensorDao.updateLastSendState(it.uniqueId, it.state.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception while updating sensors.", e)
|
||||
}
|
||||
|
||||
// We failed to update a sensor, we should re register next time
|
||||
if (!success) {
|
||||
enabledRegistrations.forEach {
|
||||
val sensor = sensorDao.get(it.uniqueId)
|
||||
if (sensor != null) {
|
||||
sensor.registered = false
|
||||
sensor.lastSentState = ""
|
||||
sensorDao.update(sensor)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else Log.d(TAG, "Nothing to update")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Context.NOTIFICATION_SERVICE
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
|
@ -19,22 +11,17 @@ import dagger.hilt.EntryPoint
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import io.homeassistant.companion.android.common.sensors.SensorReceiverBase
|
||||
import io.homeassistant.companion.android.common.sensors.SensorWorkerBase
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SensorWorker(
|
||||
private val appContext: Context,
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
companion object {
|
||||
private const val TAG = "SensorWorker"
|
||||
const val channelId = "Sensor Worker"
|
||||
const val NOTIFICATION_ID = 42
|
||||
) : SensorWorkerBase(appContext, workerParams) {
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||
|
@ -44,51 +31,28 @@ class SensorWorker(
|
|||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, sensorWorker)
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, sensorWorker)
|
||||
}
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface SensorWorkerEntryPoint {
|
||||
fun integrationRepository(): IntegrationRepository
|
||||
}
|
||||
|
||||
private val notificationManager = appContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
val sensorDao = AppDatabase.getInstance(applicationContext).sensorDao()
|
||||
val enabledSensorCount = sensorDao.getEnabledCount() ?: 0
|
||||
if (enabledSensorCount > 0) {
|
||||
Log.d(TAG, "Updating all Sensors.")
|
||||
createNotificationChannel()
|
||||
val notification = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(R.drawable.ic_stat_ic_notification)
|
||||
.setContentTitle(appContext.getString(R.string.updating_sensors))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
|
||||
val foregroundInfo = ForegroundInfo(NOTIFICATION_ID, notification)
|
||||
setForeground(foregroundInfo)
|
||||
val lastUpdateSensor = sensorDao.get(LastUpdateManager.lastUpdate.id)
|
||||
if (lastUpdateSensor?.enabled == true) {
|
||||
LastUpdateManager().sendLastUpdate(appContext, TAG)
|
||||
}
|
||||
val integrationUseCase = EntryPointAccessors.fromApplication(appContext, SensorWorkerEntryPoint::class.java).integrationRepository()
|
||||
SensorReceiver().updateSensors(appContext, integrationUseCase)
|
||||
override val integrationUseCase: IntegrationRepository
|
||||
get() {
|
||||
return EntryPointAccessors.fromApplication(
|
||||
appContext,
|
||||
SensorWorkerEntryPoint::class.java
|
||||
)
|
||||
.integrationRepository()
|
||||
}
|
||||
Result.success()
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
var notificationChannel =
|
||||
notificationManager.getNotificationChannel(channelId)
|
||||
if (notificationChannel == null) {
|
||||
notificationChannel = NotificationChannel(
|
||||
channelId, TAG, NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
override val sensorReceiver: SensorReceiverBase
|
||||
get() {
|
||||
return SensorReceiver()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.hardware.SensorManager.SENSOR_DELAY_NORMAL
|
|||
import android.os.Build
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class StepsSensorManager : SensorManager, SensorEventListener {
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.os.Environment
|
|||
import android.os.StatFs
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.homeassistant.companion.android.sensors
|
|||
|
||||
import android.content.Context
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.net.NetworkCapabilities
|
|||
import android.net.TrafficStats
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
|
|
@ -128,9 +128,10 @@ class ManageTilesFragment constructor(
|
|||
findPreference<ListPreference>("tile_entity")?.value = item.entityId
|
||||
tileEntity = item.entityId
|
||||
findPreference<Preference>("tile_icon")?.let {
|
||||
it.summary = item.iconId.toString()
|
||||
if (item.iconId != null) {
|
||||
val iconDrawable = iconPack.getIcon(item.iconId)?.drawable
|
||||
val iconId = item.iconId
|
||||
it.summary = iconId.toString()
|
||||
if (iconId != null) {
|
||||
val iconDrawable = iconPack.getIcon(iconId)?.drawable
|
||||
if (iconDrawable != null) {
|
||||
val icon = DrawableCompat.wrap(iconDrawable)
|
||||
icon.setColorFilter(
|
||||
|
|
|
@ -98,12 +98,13 @@ class EntityWidgetConfigureActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
if (!staticWidget.attributeIds.isNullOrEmpty()) {
|
||||
val attributeIds = staticWidget.attributeIds
|
||||
if (!attributeIds.isNullOrEmpty()) {
|
||||
binding.appendAttributeValueCheckbox.isChecked = true
|
||||
appendAttributes = true
|
||||
for (item in staticWidget.attributeIds.split(','))
|
||||
for (item in attributeIds.split(','))
|
||||
selectedAttributeIds.add(item)
|
||||
binding.widgetTextConfigAttribute.setText(staticWidget.attributeIds.replace(",", ", "))
|
||||
binding.widgetTextConfigAttribute.setText(attributeIds.replace(",", ", "))
|
||||
binding.attributeValueLinearLayout.visibility = VISIBLE
|
||||
binding.attributeSeparator.setText(staticWidget.attributeSeparator)
|
||||
}
|
||||
|
|
7
app/src/main/res/values/aliases_common.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="sensor" type="string">@string/sensor</item>
|
||||
<item name="sensors" type="string">@string/sensors</item>
|
||||
<item name="ic_stat_ic_notification" type="drawable">@drawable/ic_stat_ic_notification</item>
|
||||
<item name="ic_stat_ic_notification_blue" type="drawable">@drawable/ic_stat_ic_notification_blue</item>
|
||||
</resources>
|
|
@ -26,15 +26,10 @@
|
|||
<string name="basic_sensor_name_app_rx_gb">App Rx GB</string>
|
||||
<string name="basic_sensor_name_app_standby">App Standby Bucket</string>
|
||||
<string name="basic_sensor_name_app_tx_gb">App Tx GB</string>
|
||||
<string name="basic_sensor_name_battery_health">Battery Health</string>
|
||||
<string name="basic_sensor_name_battery_level">Battery Level</string>
|
||||
<string name="basic_sensor_name_battery_state">Battery State</string>
|
||||
<string name="basic_sensor_name_bluetooth">Bluetooth Connection</string>
|
||||
<string name="basic_sensor_name_bluetooth_ble_emitter">Ble Transmitter</string>
|
||||
<string name="basic_sensor_name_bluetooth_state">Bluetooth State</string>
|
||||
<string name="basic_sensor_name_call_number">Call Number</string>
|
||||
<string name="basic_sensor_name_charger_type">Charger Type</string>
|
||||
<string name="basic_sensor_name_charging">Is Charging</string>
|
||||
<string name="basic_sensor_name_current_time_zone">Current Time Zone</string>
|
||||
<string name="basic_sensor_name_current_version">Current Version</string>
|
||||
<string name="basic_sensor_name_device_locked">Device Locked</string>
|
||||
|
@ -49,7 +44,6 @@
|
|||
<string name="basic_sensor_name_last_notification">Last Notification</string>
|
||||
<string name="basic_sensor_name_last_reboot">Last Reboot</string>
|
||||
<string name="basic_sensor_name_last_removed_notification">Last Removed Notification</string>
|
||||
<string name="basic_sensor_name_last_update">Last Update Trigger</string>
|
||||
<string name="basic_sensor_name_location_accurate">Single Accurate Location</string>
|
||||
<string name="basic_sensor_name_location_background">Background Location</string>
|
||||
<string name="basic_sensor_name_location_zone">Location Zone</string>
|
||||
|
@ -101,8 +95,6 @@ to your home internet.</string>
|
|||
<string name="crash_reporting">Crash Reporting</string>
|
||||
<string name="crash_reporting_summary">Help the developers fix bugs and crashes by leaving this enabled. If the application crashes this will automatically generate and send a report. If you notice a crash also create an issue on Github!</string>
|
||||
<string name="create_template">Create Template</string>
|
||||
<string name="database_event_failure">Unable to send migration failure event to Home Assistant</string>
|
||||
<string name="database_migration_failed">Database migration failed, all sensor and widget data has been reset. Please re-enable your sensors and recreate your widgets.</string>
|
||||
<string name="delete_all_notifications">Delete all notifications from history</string>
|
||||
<string name="delete_this_notification">Delete this notification from history</string>
|
||||
<string name="delete_widget">Delete Widget</string>
|
||||
|
@ -131,7 +123,7 @@ to your home internet.</string>
|
|||
<string name="enable_all_sensors">Enable All Sensors</string>
|
||||
<string name="enable_all_sensors_summary">All required permissions will be requested upon enabling</string>
|
||||
<string name="enable_location_tracking">Enable Location Tracking</string>
|
||||
<string name="enable_location_tracking_description">Enabling this sensor will allow the Home Assistant application to track you location and report it back to your Home Assistant server. Ensure that you enable background access to location, otherwise we cannot enable location tracking. Your location data is sent directly to your Home Assistant server and is never sent to a 3rd party.</string>
|
||||
<string name="enable_location_tracking_description">Enabling this sensor will allow the Home Assistant application to track you location and report it back to your instance of Home Assistant. Ensure that you enable background access to location, otherwise we cannot enable location tracking. Your location data is sent directly to your Home Assistant instance and is never sent to a 3rd party.</string>
|
||||
<string name="enable_location_tracking_prompt">This app collects location data to enable Location Tracking, Wifi Connection Status, and URL decision making even when the app is closed or not in use.</string>
|
||||
<string name="enable_remaining_sensors">Enable %1$d Sensors</string>
|
||||
<string name="enabled_summary">When enabled values will be sent to Home Assistant</string>
|
||||
|
@ -142,7 +134,7 @@ to your home internet.</string>
|
|||
<string name="error_onboarding_connection_failed">Unable to connect to Home Assistant.</string>
|
||||
<string name="error_ssl">Unable to communicate with Home Assistant because of a SSL error. Please ensure your certificate is valid.</string>
|
||||
<string name="error_with_registration">Please check to ensure you have the mobile_app
|
||||
integration enabled on your home assistant server.</string>
|
||||
integration enabled on your home assistant instance.</string>
|
||||
<string name="event_error">Failed to notify HA of picked option.</string>
|
||||
<string name="exit">Exit</string>
|
||||
<string name="failed_scan">Scanning for Home Assistant failed.</string>
|
||||
|
@ -160,7 +152,7 @@ integration enabled on your home assistant server.</string>
|
|||
<string name="high_accuracy_mode_notification_title">High accuracy (GPS) mode enabled</string>
|
||||
<string name="history">History</string>
|
||||
<string name="home_assistant_not_discover">Unable to find your
|
||||
Home Assistant server</string>
|
||||
Home Assistant instance</string>
|
||||
<string name="icon">Icon</string>
|
||||
<string name="input_url">Home Assistant URL</string>
|
||||
<string name="input_url_hint">https://example.duckdns.org:8123</string>
|
||||
|
@ -283,9 +275,8 @@ for Home Assistant</string>
|
|||
<string name="security_vulnerably_understand">I Understand</string>
|
||||
<string name="security_vulnerably_view">View Bulletin</string>
|
||||
<string name="select_entity_to_display">Select Entity to display</string>
|
||||
<string name="select_instance">Select the server you would
|
||||
<string name="select_instance">Select the instance you would
|
||||
like to connect to:</string>
|
||||
<string name="sensor">Sensor</string>
|
||||
<string name="sensor_description">Description</string>
|
||||
<string name="sensor_description_active_notification_count">Total count of active notifications that are visible to the user including silent, persistent and the Sensor Worker notifications.</string>
|
||||
<string name="sensor_description_app_importance">If the app is in the foreground, background or any other state it can be.</string>
|
||||
|
@ -296,15 +287,10 @@ like to connect to:</string>
|
|||
<string name="sensor_description_app_tx_gb">App Tx GB since last device reboot</string>
|
||||
<string name="sensor_description_audio_mode">The state of the devices audio mode</string>
|
||||
<string name="sensor_description_audio_sensor">The state of the devices ringer mode</string>
|
||||
<string name="sensor_description_battery_health">The health of the battery</string>
|
||||
<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_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.\nBy default this sensor is not turned on with the Enable all toggle, to avoid battery drain, there is a setting to enable this (\"Include when enabling all sensors\").\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.\nSettings allow for specifying:\n- \"UUID\" (standard UUID format), \"Major\" and \"Minor\" (should be 0 - 65535), to tailor identifiers and groups\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\nNote:\nAdditionally a separate setting exists (\"Enable Transmitter\") to stop or start transmitting.</string>
|
||||
<string name="sensor_description_bluetooth_connection">Information about currently connected bluetooth devices</string>
|
||||
<string name="sensor_description_bluetooth_state">Whether or not bluetooth is enabled on the device</string>
|
||||
<string name="sensor_description_call_number">The cell number of the incoming or outgoing call</string>
|
||||
<string name="sensor_description_charger_type">The type of charger plugged into the device currently</string>
|
||||
<string name="sensor_description_charging">Whether or not the device is actively charging</string>
|
||||
<string name="sensor_description_current_time_zone">The current time zone the device is in</string>
|
||||
<string name="sensor_description_current_version">The current installed application version</string>
|
||||
<string name="sensor_description_detected_activity">The current activity type as computed by Googles Activity Recognition API</string>
|
||||
|
@ -322,7 +308,6 @@ like to connect to:</string>
|
|||
<string name="sensor_description_last_notification">The details of the last notification.</string>
|
||||
<string name="sensor_description_last_reboot">The date and time of the devices last reboot. The setting below will allow you to adjust the deadband in milliseconds, if you still find the value to jump incorrectly. The default value is 60000 (1 minute).</string>
|
||||
<string name="sensor_description_last_removed_notification">The details of the last removed notification. This can be any notification either cleared by the user or removed by an application.</string>
|
||||
<string name="sensor_description_last_update">The intent for the last update that was sent, periodic updates will show as \"SensorWorker\". Enabling the \"Add New Intent\" toggle will create 1 setting to allow you to register for a broadcast intent. The toggle will switch back to off once a new setting is created so you will need to turn it back on to save more intents. You can also clear out the setting value to remove the setting in the next update. You must restart the application after making changes to these settings to take effect.</string>
|
||||
<string name="sensor_description_light_sensor">The current level of illuminance</string>
|
||||
<string name="sensor_description_location_accurate">Allow Home Assistant to send a notification to request an accurate location along with the application periodically requesting an accurate location. The Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant. The Minimum time between updates (in milliseconds) keeps the device from sending the accurate location too often. The Include in sensor update setting will make a location request with each sensor update.</string>
|
||||
<string name="sensor_description_location_background">Update your location behind the scenes, periodically. The Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant.</string>
|
||||
|
@ -335,7 +320,6 @@ like to connect to:</string>
|
|||
<string name="sensor_description_mobile_tx_gb">Total Tx GB on cellular data since last device reboot</string>
|
||||
<string name="sensor_description_music_active">Whether or not music is actively playing on the device</string>
|
||||
<string name="sensor_description_next_alarm">The date and time of the next scheduled alarm, any app or manufacturer can override the default behavior. The package attribute will tell you which app set the next scheduled alarm. The setting below will create an Allow List so you can specify what packages you want the alarm event from. This will ignore alarm events for packages not selected and the state will not update until the next scheduled alarm matches one of the selected packages.</string>
|
||||
<string name="sensor_description_none">No description</string>
|
||||
<string name="sensor_description_phone_state">Whether or not the phone is ringing or in a call, no other caller information is stored</string>
|
||||
<string name="sensor_description_power_save">Whether or not the device is in Power Save mode</string>
|
||||
<string name="sensor_description_pressure_sensor">The current ambient air pressure reading</string>
|
||||
|
@ -368,7 +352,6 @@ like to connect to:</string>
|
|||
<string name="sensor_name_app_sensor">App Sensors</string>
|
||||
<string name="sensor_name_audio">Audio Sensors</string>
|
||||
<string name="sensor_name_audio_mode">Audio Mode</string>
|
||||
<string name="sensor_name_battery">Battery Sensors</string>
|
||||
<string name="sensor_name_bluetooth">Bluetooth Sensors</string>
|
||||
<string name="sensor_name_dnd">Do Not Disturb Sensor</string>
|
||||
<string name="sensor_name_geolocation">Geolocation Sensors</string>
|
||||
|
@ -376,7 +359,6 @@ like to connect to:</string>
|
|||
<string name="sensor_name_keyguard">Keyguard Sensors</string>
|
||||
<string name="sensor_name_last_notification">Notification Sensors</string>
|
||||
<string name="sensor_name_last_reboot">Last Reboot Sensor</string>
|
||||
<string name="sensor_name_last_update">Last Update Sensor</string>
|
||||
<string name="sensor_name_light">Light Sensor</string>
|
||||
<string name="sensor_name_location">Location Sensors</string>
|
||||
<string name="sensor_name_mic_muted">Mic Muted</string>
|
||||
|
@ -401,7 +383,6 @@ like to connect to:</string>
|
|||
<string name="sensor_settings">Sensor Settings</string>
|
||||
<string name="sensor_summary">Use this to manage what sensors are enabled/disabled.</string>
|
||||
<string name="sensor_title">Manage Sensors</string>
|
||||
<string name="sensors">Sensors</string>
|
||||
<string name="sensors_with_settings">The following sensors offer custom settings: %1$s</string>
|
||||
<string name="session_timeout_title">Session TimeOut (in seconds)</string>
|
||||
<string name="set_lock_message">No biometric sensor or screenlock credential available</string>
|
||||
|
@ -470,7 +451,6 @@ like to connect to:</string>
|
|||
<string name="unable_to_register">Unable to Register Application</string>
|
||||
<string name="unique_id">Unique Id</string>
|
||||
<string name="update_widget">Update Widget</string>
|
||||
<string name="updating_sensors">Updating Sensors</string>
|
||||
<string name="unknown_address">Unknown address</string>
|
||||
<string name="url_invalid">Url Invalid</string>
|
||||
<string name="url_parse_error">Unable to parse your Home Assistant URL. It should look like https://example.com</string>
|
||||
|
@ -517,8 +497,6 @@ like to connect to:</string>
|
|||
<string name="zone_event_failure">Unable to send zone event to Home Assistant</string>
|
||||
<string name="what_is_this">What is this?</string>
|
||||
<string name="what_is_this_crash">Unable to load Home Assistant home page, do you have a browser installed?</string>
|
||||
<string name="basic_sensor_name_battery_temperature">Battery Temperature</string>
|
||||
<string name="sensor_description_battery_temperature">The current battery temperature</string>
|
||||
<string name="shortcut_pinned_desc">Pinned Shortcut Description</string>
|
||||
<string name="shortcut_pinned_label">Pinned Shortcut Label</string>
|
||||
<string name="shortcut_pinned_id">Pinned Shortcut ID</string>
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class ActivitySensorManager : BroadcastReceiver(), SensorManager {
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.homeassistant.companion.android.sensors
|
|||
|
||||
import android.content.Context
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class GeocodeSensorManager : SensorManager {
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.sensors.LocationSensorManagerBase
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
|
||||
class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||
class LocationSensorManager : LocationSensorManagerBase(), SensorManager {
|
||||
|
||||
companion object {
|
||||
const val MINIMUM_ACCURACY = 200
|
||||
|
@ -41,8 +41,6 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
|||
internal const val TAG = "LocBroadcastReceiver"
|
||||
}
|
||||
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// Noop
|
||||
}
|
||||
|
|
|
@ -20,6 +20,30 @@ android {
|
|||
buildConfigField("String", "PUSH_URL", "\"$homeAssistantAndroidPushUrl\"")
|
||||
buildConfigField("String", "RATE_LIMIT_URL", "\"$homeAssistantAndroidRateLimitUrl\"")
|
||||
buildConfigField("String", "VERSION_NAME", "\"$versionName-$versionCode\"")
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments(mapOf("room.incremental" to "true"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility(JavaVersion.VERSION_11)
|
||||
targetCompatibility(JavaVersion.VERSION_11)
|
||||
}
|
||||
|
||||
lint {
|
||||
isAbortOnError = false
|
||||
disable("MissingTranslation")
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +55,12 @@ dependencies {
|
|||
implementation("com.google.dagger:hilt-android:2.40.1")
|
||||
kapt("com.google.dagger:hilt-android-compiler:2.40.1")
|
||||
|
||||
api("androidx.room:room-runtime:2.3.0")
|
||||
api("androidx.room:room-ktx:2.3.0")
|
||||
kapt("androidx.room:room-compiler:2.3.0")
|
||||
|
||||
api("androidx.work:work-runtime-ktx:2.7.0")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-jackson:2.9.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.9.3")
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.BatteryManager
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.R
|
||||
|
||||
class BatterySensorManager : SensorManager {
|
||||
|
||||
|
@ -65,13 +65,22 @@ class BatterySensorManager : SensorManager {
|
|||
override fun docsLink(): String {
|
||||
return "https://companion.home-assistant.io/docs/core/sensors#battery-sensors"
|
||||
}
|
||||
|
||||
override val enabledByDefault: Boolean
|
||||
get() = true
|
||||
|
||||
override val name: Int
|
||||
get() = R.string.sensor_name_battery
|
||||
|
||||
override fun getAvailableSensors(context: Context): List<SensorManager.BasicSensor> {
|
||||
return listOf(batteryLevel, batteryState, isChargingState, chargerTypeState, batteryHealthState, batteryTemperature)
|
||||
return listOf(
|
||||
batteryLevel,
|
||||
batteryState,
|
||||
isChargingState,
|
||||
chargerTypeState,
|
||||
batteryHealthState,
|
||||
batteryTemperature
|
||||
)
|
||||
}
|
||||
|
||||
override fun requiredPermissions(sensorId: String): Array<String> {
|
|
@ -1,83 +1,83 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
|
||||
class LastUpdateManager : SensorManager {
|
||||
companion object {
|
||||
private const val TAG = "LastUpdate"
|
||||
private const val SETTING_ADD_NEW_INTENT = "lastupdate_add_new_intent"
|
||||
|
||||
val lastUpdate = SensorManager.BasicSensor(
|
||||
"last_update",
|
||||
"sensor",
|
||||
R.string.basic_sensor_name_last_update,
|
||||
R.string.sensor_description_last_update,
|
||||
entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC
|
||||
)
|
||||
}
|
||||
|
||||
override fun docsLink(): String {
|
||||
return "https://companion.home-assistant.io/docs/core/sensors#last-update-trigger-sensor"
|
||||
}
|
||||
override val enabledByDefault: Boolean
|
||||
get() = false
|
||||
override val name: Int
|
||||
get() = R.string.sensor_name_last_update
|
||||
|
||||
override fun getAvailableSensors(context: Context): List<SensorManager.BasicSensor> {
|
||||
return listOf(lastUpdate)
|
||||
}
|
||||
|
||||
override fun requiredPermissions(sensorId: String): Array<String> {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
override fun requestSensorUpdate(
|
||||
context: Context
|
||||
) {
|
||||
// No op
|
||||
}
|
||||
|
||||
fun sendLastUpdate(context: Context, intentAction: String?) {
|
||||
|
||||
if (!isEnabled(context, lastUpdate.id))
|
||||
return
|
||||
|
||||
if (intentAction.isNullOrEmpty())
|
||||
return
|
||||
|
||||
val icon = "mdi:update"
|
||||
|
||||
Log.d(TAG, "Last update is $intentAction")
|
||||
|
||||
onSensorUpdated(
|
||||
context,
|
||||
lastUpdate,
|
||||
intentAction,
|
||||
icon,
|
||||
mapOf()
|
||||
)
|
||||
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val allSettings = sensorDao.getSettings(lastUpdate.id)
|
||||
val intentSettingName = "lastupdate_intent_var1:${allSettings.size}:"
|
||||
val addNewIntent = allSettings.firstOrNull { it.name == SETTING_ADD_NEW_INTENT }?.value ?: "false"
|
||||
val intentSetting = allSettings.firstOrNull { it.name == intentSettingName }?.value ?: ""
|
||||
if (addNewIntent == "true") {
|
||||
if (intentSetting == "") {
|
||||
sensorDao.add(Setting(lastUpdate.id, SETTING_ADD_NEW_INTENT, "false", "toggle"))
|
||||
sensorDao.add(Setting(lastUpdate.id, intentSettingName, intentAction, "string"))
|
||||
}
|
||||
} else {
|
||||
sensorDao.add(Setting(lastUpdate.id, SETTING_ADD_NEW_INTENT, "false", "toggle"))
|
||||
}
|
||||
for (setting in allSettings) {
|
||||
if (setting.value == "")
|
||||
sensorDao.removeSetting(lastUpdate.id, setting.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Setting
|
||||
|
||||
class LastUpdateManager : SensorManager {
|
||||
companion object {
|
||||
private const val TAG = "LastUpdate"
|
||||
private const val SETTING_ADD_NEW_INTENT = "lastupdate_add_new_intent"
|
||||
|
||||
val lastUpdate = SensorManager.BasicSensor(
|
||||
"last_update",
|
||||
"sensor",
|
||||
R.string.basic_sensor_name_last_update,
|
||||
R.string.sensor_description_last_update,
|
||||
entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC
|
||||
)
|
||||
}
|
||||
|
||||
override fun docsLink(): String {
|
||||
return "https://companion.home-assistant.io/docs/core/sensors#last-update-trigger-sensor"
|
||||
}
|
||||
override val enabledByDefault: Boolean
|
||||
get() = false
|
||||
override val name: Int
|
||||
get() = R.string.sensor_name_last_update
|
||||
|
||||
override fun getAvailableSensors(context: Context): List<SensorManager.BasicSensor> {
|
||||
return listOf(lastUpdate)
|
||||
}
|
||||
|
||||
override fun requiredPermissions(sensorId: String): Array<String> {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
override fun requestSensorUpdate(
|
||||
context: Context
|
||||
) {
|
||||
// No op
|
||||
}
|
||||
|
||||
fun sendLastUpdate(context: Context, intentAction: String?) {
|
||||
|
||||
if (!isEnabled(context, lastUpdate.id))
|
||||
return
|
||||
|
||||
if (intentAction.isNullOrEmpty())
|
||||
return
|
||||
|
||||
val icon = "mdi:update"
|
||||
|
||||
Log.d(TAG, "Last update is $intentAction")
|
||||
|
||||
onSensorUpdated(
|
||||
context,
|
||||
lastUpdate,
|
||||
intentAction,
|
||||
icon,
|
||||
mapOf()
|
||||
)
|
||||
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val allSettings = sensorDao.getSettings(lastUpdate.id)
|
||||
val intentSettingName = "lastupdate_intent_var1:${allSettings.size}:"
|
||||
val addNewIntent = allSettings.firstOrNull { it.name == SETTING_ADD_NEW_INTENT }?.value ?: "false"
|
||||
val intentSetting = allSettings.firstOrNull { it.name == intentSettingName }?.value ?: ""
|
||||
if (addNewIntent == "true") {
|
||||
if (intentSetting == "") {
|
||||
sensorDao.add(Setting(lastUpdate.id, SETTING_ADD_NEW_INTENT, "false", "toggle"))
|
||||
sensorDao.add(Setting(lastUpdate.id, intentSettingName, intentAction, "string"))
|
||||
}
|
||||
} else {
|
||||
sensorDao.add(Setting(lastUpdate.id, SETTING_ADD_NEW_INTENT, "false", "toggle"))
|
||||
}
|
||||
for (setting in allSettings) {
|
||||
if (setting.value == "")
|
||||
sensorDao.removeSetting(lastUpdate.id, setting.name)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class LocationSensorManagerBase : BroadcastReceiver(), SensorManager {
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Process.myPid
|
||||
import android.os.Process.myUid
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.sensor.Attribute
|
||||
import io.homeassistant.companion.android.database.sensor.Sensor
|
|
@ -0,0 +1,181 @@
|
|||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.util.Log
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.data.integration.SensorRegistration
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SensorReceiverBase : BroadcastReceiver() {
|
||||
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
protected abstract val tag: String
|
||||
protected abstract val currentAppVersion: String
|
||||
protected abstract val managers: List<SensorManager>
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
private val chargingActions = listOf(
|
||||
Intent.ACTION_BATTERY_LOW,
|
||||
Intent.ACTION_BATTERY_OKAY,
|
||||
Intent.ACTION_POWER_CONNECTED,
|
||||
Intent.ACTION_POWER_DISCONNECTED
|
||||
)
|
||||
|
||||
protected abstract val skippableActions: Map<String, String>
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (skippableActions.containsKey(intent.action)) {
|
||||
val sensor = skippableActions[intent.action]
|
||||
if (!isSensorEnabled(context, sensor!!)) {
|
||||
Log.d(
|
||||
tag,
|
||||
String.format
|
||||
(
|
||||
"Sensor %s corresponding to received event %s is disabled, skipping sensors update",
|
||||
sensor,
|
||||
intent.action
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (isSensorEnabled(context, LastUpdateManager.lastUpdate.id)) {
|
||||
LastUpdateManager().sendLastUpdate(context, intent.action)
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val allSettings = sensorDao.getSettings(LastUpdateManager.lastUpdate.id)
|
||||
for (setting in allSettings) {
|
||||
if (setting.value != "" && intent.action == setting.value) {
|
||||
val eventData = intent.extras?.keySet()?.map { it.toString() to intent.extras?.get(it).toString() }?.toMap()?.plus("intent" to intent.action.toString())
|
||||
?: mapOf("intent" to intent.action.toString())
|
||||
Log.d(tag, "Event data: $eventData")
|
||||
ioScope.launch {
|
||||
try {
|
||||
integrationUseCase.fireEvent(
|
||||
"android.intent_received",
|
||||
eventData as Map<String, Any>
|
||||
)
|
||||
Log.d(tag, "Event successfully sent to Home Assistant")
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
tag,
|
||||
"Unable to send event data to Home Assistant",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ioScope.launch {
|
||||
updateSensors(context, integrationUseCase)
|
||||
if (chargingActions.contains(intent.action)) {
|
||||
// Add a 5 second delay to perform another update so charging state updates completely.
|
||||
// This is necessary as the system needs a few seconds to verify the charger.
|
||||
delay(5000L)
|
||||
updateSensors(context, integrationUseCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun isSensorEnabled(context: Context, id: String): Boolean {
|
||||
return AppDatabase.getInstance(context).sensorDao().get(id)?.enabled == true
|
||||
}
|
||||
|
||||
suspend fun updateSensors(
|
||||
context: Context,
|
||||
integrationUseCase: IntegrationRepository
|
||||
) {
|
||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
||||
val enabledRegistrations = mutableListOf<SensorRegistration<Any>>()
|
||||
|
||||
val checkDeviceRegistration = integrationUseCase.getRegistration()
|
||||
if (checkDeviceRegistration.appVersion == null) {
|
||||
Log.w(tag, "Device not registered, skipping sensor update/registration")
|
||||
return
|
||||
}
|
||||
|
||||
val currentHAversion = integrationUseCase.getHomeAssistantVersion()
|
||||
|
||||
managers.forEach { manager ->
|
||||
|
||||
// Since we don't have this manager injected it doesn't fulfil it's injects, manually
|
||||
// inject for now I guess?
|
||||
if (manager is LocationSensorManagerBase)
|
||||
manager.integrationUseCase = integrationUseCase
|
||||
|
||||
try {
|
||||
manager.requestSensorUpdate(context)
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Issue requesting updates for ${context.getString(manager.name)}", e)
|
||||
}
|
||||
manager.getAvailableSensors(context).forEach { basicSensor ->
|
||||
val fullSensor = sensorDao.getFull(basicSensor.id)
|
||||
val sensor = fullSensor?.sensor
|
||||
val sensorCoreRegistration = sensor?.coreRegistration
|
||||
val sensorAppRegistration = sensor?.appRegistration
|
||||
|
||||
// Always register enabled sensors in case of available entity updates
|
||||
// when app or core version change is detected every 4 hours
|
||||
if (sensor?.enabled == true && sensor.type.isNotBlank() && sensor.icon.isNotBlank() &&
|
||||
(currentAppVersion != sensorAppRegistration || currentHAversion != sensorCoreRegistration || !sensor.registered)
|
||||
) {
|
||||
val reg = fullSensor.toSensorRegistration()
|
||||
val config = Configuration(context.resources.configuration)
|
||||
config.setLocale(Locale("en"))
|
||||
reg.name = context.createConfigurationContext(config).resources.getString(basicSensor.name)
|
||||
|
||||
try {
|
||||
integrationUseCase.registerSensor(reg)
|
||||
sensor.registered = true
|
||||
sensor.coreRegistration = currentHAversion
|
||||
sensor.appRegistration = currentAppVersion
|
||||
sensorDao.update(sensor)
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Issue registering sensor: ${reg.uniqueId}", e)
|
||||
}
|
||||
}
|
||||
if (sensor?.enabled == true && sensor.registered && sensor.state != sensor.lastSentState) {
|
||||
enabledRegistrations.add(fullSensor.toSensorRegistration())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledRegistrations.isNotEmpty()) {
|
||||
var success = false
|
||||
try {
|
||||
success = integrationUseCase.updateSensors(enabledRegistrations.toTypedArray())
|
||||
enabledRegistrations.forEach {
|
||||
sensorDao.updateLastSendState(it.uniqueId, it.state.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Exception while updating sensors.", e)
|
||||
}
|
||||
|
||||
// We failed to update a sensor, we should re register next time
|
||||
if (!success) {
|
||||
enabledRegistrations.forEach {
|
||||
val sensor = sensorDao.get(it.uniqueId)
|
||||
if (sensor != null) {
|
||||
sensor.registered = false
|
||||
sensor.lastSentState = ""
|
||||
sensorDao.update(sensor)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else Log.d(tag, "Nothing to update")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package io.homeassistant.companion.android.common.sensors
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Context.NOTIFICATION_SERVICE
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
abstract class SensorWorkerBase(
|
||||
val appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
protected abstract val integrationUseCase: IntegrationRepository
|
||||
protected abstract val sensorReceiver: SensorReceiverBase
|
||||
|
||||
companion object {
|
||||
const val TAG = "SensorWorker"
|
||||
const val channelId = "Sensor Worker"
|
||||
const val NOTIFICATION_ID = 42
|
||||
}
|
||||
|
||||
private val notificationManager = appContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
val sensorDao = AppDatabase.getInstance(applicationContext).sensorDao()
|
||||
val enabledSensorCount = sensorDao.getEnabledCount() ?: 0
|
||||
if (enabledSensorCount > 0) {
|
||||
Log.d(TAG, "Updating all Sensors.")
|
||||
createNotificationChannel()
|
||||
val notification = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(R.drawable.ic_stat_ic_notification)
|
||||
.setContentTitle(appContext.getString(R.string.updating_sensors))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
|
||||
val foregroundInfo = ForegroundInfo(NOTIFICATION_ID, notification)
|
||||
setForeground(foregroundInfo)
|
||||
val lastUpdateSensor = sensorDao.get(LastUpdateManager.lastUpdate.id)
|
||||
if (lastUpdateSensor != null) {
|
||||
if (lastUpdateSensor.enabled)
|
||||
LastUpdateManager().sendLastUpdate(appContext, TAG)
|
||||
}
|
||||
sensorReceiver.updateSensors(appContext, integrationUseCase)
|
||||
}
|
||||
Result.success()
|
||||
}
|
||||
|
||||
protected fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
var notificationChannel =
|
||||
notificationManager.getNotificationChannel(channelId)
|
||||
if (notificationChannel == null) {
|
||||
notificationChannel = NotificationChannel(
|
||||
channelId, TAG, NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import androidx.room.RoomDatabase
|
|||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.database.authentication.Authentication
|
||||
import io.homeassistant.companion.android.database.authentication.AuthenticationDao
|
|
@ -1,21 +1,21 @@
|
|||
package io.homeassistant.companion.android.database.qs
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "qs_tiles")
|
||||
data class TileEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Int,
|
||||
@ColumnInfo(name = "tileId")
|
||||
val tileId: String,
|
||||
@ColumnInfo(name = "icon_id")
|
||||
val iconId: Int?,
|
||||
@ColumnInfo(name = "entityId")
|
||||
val entityId: String,
|
||||
@ColumnInfo(name = "label")
|
||||
val label: String,
|
||||
@ColumnInfo(name = "subtitle")
|
||||
val subtitle: String?
|
||||
)
|
||||
package io.homeassistant.companion.android.database.qs
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "qs_tiles")
|
||||
data class TileEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Int,
|
||||
@ColumnInfo(name = "tileId")
|
||||
val tileId: String,
|
||||
@ColumnInfo(name = "icon_id")
|
||||
val iconId: Int?,
|
||||
@ColumnInfo(name = "entityId")
|
||||
val entityId: String,
|
||||
@ColumnInfo(name = "label")
|
||||
val label: String,
|
||||
@ColumnInfo(name = "subtitle")
|
||||
val subtitle: String?
|
||||
)
|
Before Width: | Height: | Size: 934 B After Width: | Height: | Size: 934 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 620 B |
Before Width: | Height: | Size: 910 B After Width: | Height: | Size: 910 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
25
common/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="basic_sensor_name_battery_health">Battery Health</string>
|
||||
<string name="basic_sensor_name_battery_level">Battery Level</string>
|
||||
<string name="basic_sensor_name_battery_state">Battery State</string>
|
||||
<string name="basic_sensor_name_battery_temperature">Battery Temperature</string>
|
||||
<string name="basic_sensor_name_charger_type">Charger Type</string>
|
||||
<string name="basic_sensor_name_charging">Is Charging</string>
|
||||
<string name="basic_sensor_name_last_update">Last Update Trigger</string>
|
||||
<string name="database_event_failure">Unable to send migration failure event to Home Assistant</string>
|
||||
<string name="database_migration_failed">Database migration failed, all sensor and widget data has been reset. Please re-enable your sensors and recreate your widgets.</string>
|
||||
<string name="sensor">Sensor</string>
|
||||
<string name="sensors">Sensors</string>
|
||||
<string name="sensor_description_none">No description</string>
|
||||
<string name="updating_sensors">Updating Sensors</string>
|
||||
<string name="sensor_description_battery_health">The health of the battery</string>
|
||||
<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_charger_type">The type of charger plugged into the device currently</string>
|
||||
<string name="sensor_description_charging">Whether or not the device is actively charging</string>
|
||||
<string name="sensor_description_last_update">The intent for the last update that was sent, periodic updates will show as \"SensorWorker\". Enabling the \"Add New Intent\" toggle will create 1 setting to allow you to register for a broadcast intent. The toggle will switch back to off once a new setting is created so you will need to turn it back on to save more intents. You can also clear out the setting value to remove the setting in the next update. You must restart the application after making changes to these settings to take effect.</string>
|
||||
<string name="sensor_name_battery">Battery Sensors</string>
|
||||
<string name="sensor_name_last_update">Last Update Sensor</string>
|
||||
</resources>
|
|
@ -69,6 +69,10 @@ android {
|
|||
lint {
|
||||
disable("MissingTranslation")
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
}
|
||||
|
||||
play {
|
||||
|
@ -102,6 +106,7 @@ dependencies {
|
|||
implementation("com.mikepenz:community-material-typeface:6.4.95.0-kotlin@aar")
|
||||
implementation("com.mikepenz:iconics-compose:5.3.3")
|
||||
|
||||
implementation("androidx.activity:activity:1.4.0")
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
implementation("androidx.compose.compiler:compiler:1.0.5")
|
||||
implementation("androidx.compose.foundation:foundation:1.0.5")
|
||||
|
|
|
@ -18,6 +18,18 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.HomeAssistant"
|
||||
android:fullBackupContent="@xml/backup_rules">
|
||||
<!-- Start things like SensorWorker on device boot -->
|
||||
<receiver android:name=".sensors.SensorReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<uses-library
|
||||
android:name="com.google.android.wearable"
|
||||
android:required="true" />
|
||||
|
|
|
@ -1,7 +1,28 @@
|
|||
package io.homeassistant.companion.android
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
|
||||
@HiltAndroidApp
|
||||
open class HomeAssistantApplication : Application()
|
||||
open class HomeAssistantApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val sensorReceiver = SensorReceiver()
|
||||
// This will cause the sensor to be updated every time the OS broadcasts that a cable was plugged/unplugged.
|
||||
// This should be nearly instantaneous allowing automations to fire immediately when a phone is plugged
|
||||
// in or unplugged. Updates will also be triggered when the system reports low battery and when it recovers.
|
||||
registerReceiver(
|
||||
sensorReceiver,
|
||||
IntentFilter().apply {
|
||||
addAction(Intent.ACTION_BATTERY_LOW)
|
||||
addAction(Intent.ACTION_BATTERY_OKAY)
|
||||
addAction(Intent.ACTION_POWER_CONNECTED)
|
||||
addAction(Intent.ACTION_POWER_DISCONNECTED)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import io.homeassistant.companion.android.home.views.LoadHomePage
|
||||
import io.homeassistant.companion.android.onboarding.OnboardingActivity
|
||||
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationActivity
|
||||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
import io.homeassistant.companion.android.sensors.SensorWorker
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -46,6 +48,22 @@ class HomeActivity : ComponentActivity(), HomeView {
|
|||
override fun onResume() {
|
||||
mainViewModel.updateFavorites()
|
||||
super.onResume()
|
||||
SensorWorker.start(this)
|
||||
|
||||
initAllSensors()
|
||||
}
|
||||
|
||||
private fun initAllSensors() {
|
||||
for (manager in SensorReceiver.MANAGERS) {
|
||||
for (basicSensor in manager.getAvailableSensors(this)) {
|
||||
manager.isEnabled(this, basicSensor.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
SensorWorker.start(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
import io.homeassistant.companion.android.common.sensors.BatterySensorManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorReceiverBase
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SensorReceiver : SensorReceiverBase() {
|
||||
|
||||
override val tag: String
|
||||
get() = TAG
|
||||
|
||||
override val currentAppVersion: String
|
||||
get() = BuildConfig.VERSION_NAME
|
||||
|
||||
override val managers: List<SensorManager>
|
||||
get() = MANAGERS
|
||||
|
||||
companion object {
|
||||
const val TAG = "SensorReceiver"
|
||||
val MANAGERS = listOf(
|
||||
BatterySensorManager()
|
||||
)
|
||||
|
||||
const val ACTION_REQUEST_SENSORS_UPDATE =
|
||||
"io.homeassistant.companion.android.background.REQUEST_SENSORS_UPDATE"
|
||||
}
|
||||
|
||||
// Suppress Lint because we only register for the receiver if the android version matches the intent
|
||||
@SuppressLint("InlinedApi")
|
||||
override val skippableActions = mapOf<String, String>()
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package io.homeassistant.companion.android.sensors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.common.sensors.SensorReceiverBase
|
||||
import io.homeassistant.companion.android.common.sensors.SensorWorkerBase
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SensorWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : SensorWorkerBase(appContext, workerParams) {
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||
|
||||
val sensorWorker =
|
||||
PeriodicWorkRequestBuilder<SensorWorker>(15, TimeUnit.MINUTES)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, sensorWorker)
|
||||
}
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface SensorWorkerEntryPoint {
|
||||
fun integrationRepository(): IntegrationRepository
|
||||
}
|
||||
|
||||
override val integrationUseCase: IntegrationRepository
|
||||
get() {
|
||||
return EntryPointAccessors.fromApplication(
|
||||
appContext,
|
||||
SensorWorkerEntryPoint::class.java
|
||||
)
|
||||
.integrationRepository()
|
||||
}
|
||||
|
||||
override val sensorReceiver: SensorReceiverBase
|
||||
get() {
|
||||
return SensorReceiver()
|
||||
}
|
||||
}
|