diff --git a/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt b/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt index ef9a14ab6..a12d69390 100644 --- a/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt +++ b/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt @@ -8,6 +8,7 @@ import android.content.IntentFilter import android.media.AudioManager import android.net.wifi.WifiManager import android.os.Build +import android.os.PowerManager import android.telephony.TelephonyManager import io.homeassistant.companion.android.common.dagger.AppComponent import io.homeassistant.companion.android.common.dagger.Graph @@ -39,6 +40,24 @@ open class HomeAssistantApplication : Application(), GraphComponentAccessor { } ) + // This will cause interactive and power save to update upon a state change + registerReceiver( + sensorReceiver, IntentFilter().apply { + addAction(Intent.ACTION_SCREEN_OFF) + addAction(Intent.ACTION_SCREEN_ON) + addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED) + } + ) + + // Update doze mode immediately on supported devices + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + registerReceiver( + sensorReceiver, IntentFilter().apply { + addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED) + } + ) + } + // This will trigger an update any time the wifi state has changed registerReceiver( sensorReceiver, diff --git a/app/src/main/java/io/homeassistant/companion/android/database/sensor/SensorWithAttributes.kt b/app/src/main/java/io/homeassistant/companion/android/database/sensor/SensorWithAttributes.kt index 163d6ee45..5b3647f16 100644 --- a/app/src/main/java/io/homeassistant/companion/android/database/sensor/SensorWithAttributes.kt +++ b/app/src/main/java/io/homeassistant/companion/android/database/sensor/SensorWithAttributes.kt @@ -17,13 +17,15 @@ data class SensorWithAttributes( val attributes = attributes.map { it.name to it.value }.toMap() val state = when (sensor.stateType) { "" -> "" + "boolean" -> sensor.state.toBoolean() + "float" -> sensor.state.toFloat() + "int" -> sensor.state.toInt() "string" -> sensor.state - "number" -> sensor.state.toFloat() else -> throw IllegalArgumentException("State is of unknown type: ${sensor.stateType}") } return SensorRegistration( sensor.id, - sensor.state, + state, sensor.type, sensor.icon, attributes, diff --git a/app/src/main/java/io/homeassistant/companion/android/sensors/PowerSensorManager.kt b/app/src/main/java/io/homeassistant/companion/android/sensors/PowerSensorManager.kt new file mode 100644 index 000000000..904232c30 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/sensors/PowerSensorManager.kt @@ -0,0 +1,115 @@ +package io.homeassistant.companion.android.sensors + +import android.content.Context +import android.content.Context.POWER_SERVICE +import android.os.Build +import android.os.PowerManager +import androidx.annotation.RequiresApi +import io.homeassistant.companion.android.R + +class PowerSensorManager : SensorManager { + companion object { + private const val TAG = "PowerSensors" + private const val packageName = "io.homeassistant.companion.android" + + private val interactiveDevice = SensorManager.BasicSensor( + "is_interactive", + "binary_sensor", + R.string.basic_sensor_name_interactive, + R.string.sensor_description_interactive + ) + private val doze = SensorManager.BasicSensor( + "is_idle", + "binary_sensor", + R.string.basic_sensor_name_doze, + R.string.sensor_description_doze + ) + private val powerSave = SensorManager.BasicSensor( + "power_save", + "binary_sensor", + R.string.basic_sensor_name_power_save, + R.string.sensor_description_power_save + ) + } + + override val enabledByDefault: Boolean + get() = false + override val name: Int + get() = R.string.sensor_name_power + + override val availableSensors: List + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + listOf(interactiveDevice, doze, powerSave) + } else { + listOf(interactiveDevice, powerSave) + } + + override fun requiredPermissions(): Array { + return emptyArray() + } + + override fun requestSensorUpdate( + context: Context + ) { + val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager + updateInteractive(context, powerManager) + updatePowerSave(context, powerManager) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + updateDoze(context, powerManager) + } + } + + private fun updateInteractive(context: Context, powerManager: PowerManager) { + + if (!isEnabled(context, interactiveDevice.id)) + return + + val interactiveState = powerManager.isInteractive + val icon = if (interactiveState) "mdi:cellphone" else "mdi:cellphone-off" + + onSensorUpdated( + context, + interactiveDevice, + interactiveState, + icon, + mapOf() + ) + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun updateDoze(context: Context, powerManager: PowerManager) { + + if (!isEnabled(context, doze.id)) + return + + val dozeState = powerManager.isDeviceIdleMode + val icon = if (dozeState) "mdi:sleep" else "mdi:sleep-off" + + onSensorUpdated( + context, + doze, + dozeState, + icon, + mapOf( + "ignoring_battery_optizimations" to powerManager.isIgnoringBatteryOptimizations(packageName) + ) + ) + } + + private fun updatePowerSave(context: Context, powerManager: PowerManager) { + + if (!isEnabled(context, powerSave.id)) + return + + val powerSaveState = powerManager.isPowerSaveMode + val icon = "mdi:battery-plus" + + onSensorUpdated( + context, + powerSave, + powerSaveState, + icon, + mapOf() + ) + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/sensors/SensorManager.kt b/app/src/main/java/io/homeassistant/companion/android/sensors/SensorManager.kt index 623555b0d..445363278 100644 --- a/app/src/main/java/io/homeassistant/companion/android/sensors/SensorManager.kt +++ b/app/src/main/java/io/homeassistant/companion/android/sensors/SensorManager.kt @@ -71,8 +71,10 @@ interface SensorManager { sensor.id = basicSensor.id sensor.state = state.toString() sensor.stateType = when (state) { + is Boolean -> "boolean" + is Int -> "int" + is Number -> "float" is String -> "string" - is Number -> "number" else -> throw IllegalArgumentException("Unknown Sensor State Type") } sensor.type = basicSensor.type diff --git a/app/src/main/java/io/homeassistant/companion/android/sensors/SensorReceiver.kt b/app/src/main/java/io/homeassistant/companion/android/sensors/SensorReceiver.kt index e7d835841..be3654eea 100644 --- a/app/src/main/java/io/homeassistant/companion/android/sensors/SensorReceiver.kt +++ b/app/src/main/java/io/homeassistant/companion/android/sensors/SensorReceiver.kt @@ -32,6 +32,7 @@ class SensorReceiver : BroadcastReceiver() { NetworkSensorManager(), NextAlarmManager(), PhoneStateSensorManager(), + PowerSensorManager(), PressureSensorManager(), ProximitySensorManager(), StepsSensorManager(), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 637ba3c2c..2dcc5aaa8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,6 +154,13 @@ like to connect to: Import existing Home Assistant zones as geofences for zone based tracking. 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. No description + Whether or not the device is in an interactive state + Whether or not the device is in Doze mode + Whether or not the device is in Power Save mode + Power Sensors + Power Save + Interactive + Doze Mode Detected Activity Geocoded Location Background Location