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 680e7faee..da1e0e675 100644 --- a/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt +++ b/app/src/main/java/io/homeassistant/companion/android/HomeAssistantApplication.kt @@ -125,6 +125,7 @@ open class HomeAssistantApplication : Application() { IntentFilter().apply { addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION) addAction(WifiManager.WIFI_STATE_CHANGED_ACTION) + addAction("android.net.wifi.WIFI_AP_STATE_CHANGED") } ) 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 79091df71..27178e2a9 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 @@ -105,6 +105,7 @@ class SensorReceiver : SensorReceiverBase() { "android.bluetooth.device.action.ACL_CONNECTED" to BluetoothSensorManager.bluetoothConnection.id, "android.bluetooth.device.action.ACL_DISCONNECTED" to BluetoothSensorManager.bluetoothConnection.id, "com.oculus.intent.action.MOUNT_STATE_CHANGED" to QuestSensorManager.headsetMounted.id, + "android.net.wifi.WIFI_AP_STATE_CHANGED" to NetworkSensorManager.hotspotState.id, BluetoothAdapter.ACTION_STATE_CHANGED to BluetoothSensorManager.bluetoothState.id, Intent.ACTION_SCREEN_OFF to PowerSensorManager.interactiveDevice.id, Intent.ACTION_SCREEN_ON to PowerSensorManager.interactiveDevice.id, diff --git a/common/src/main/java/io/homeassistant/companion/android/common/sensors/NetworkSensorManager.kt b/common/src/main/java/io/homeassistant/companion/android/common/sensors/NetworkSensorManager.kt index b00a6b6a9..4a3aed5a4 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/sensors/NetworkSensorManager.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/sensors/NetworkSensorManager.kt @@ -3,6 +3,7 @@ package io.homeassistant.companion.android.common.sensors import android.Manifest import android.annotation.SuppressLint import android.content.Context +import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.wifi.WifiInfo @@ -24,11 +25,21 @@ import okhttp3.Response import okio.IOException import org.json.JSONException import org.json.JSONObject +import java.lang.reflect.Method import io.homeassistant.companion.android.common.R as commonR class NetworkSensorManager : SensorManager { companion object { private const val TAG = "NetworkSM" + val hotspotState = SensorManager.BasicSensor( + "hotspot_state", + "binary_sensor", + commonR.string.basic_sensor_name_hotspot_state, + commonR.string.sensor_description_hotspot, + "mdi:access-point", + entityCategory = SensorManager.ENTITY_CATEGORY_DIAGNOSTIC, + updateType = SensorManager.BasicSensor.UpdateType.INTENT + ) val wifiConnection = SensorManager.BasicSensor( "wifi_connection", "sensor", @@ -137,7 +148,12 @@ class NetworkSensorManager : SensorManager { wifiSignalStrength ) val list = if (hasWifi(context)) { - wifiSensors.plus(publicIp) + val withPublicIp = wifiSensors.plus(publicIp) + if (hasHotspot(context)) { + withPublicIp.plus(hotspotState) + } else { + withPublicIp + } } else { listOf(publicIp) } @@ -150,7 +166,7 @@ class NetworkSensorManager : SensorManager { override fun requiredPermissions(sensorId: String): Array { return when { - sensorId == publicIp.id || sensorId == networkType.id -> { + sensorId == hotspotState.id || sensorId == publicIp.id || sensorId == networkType.id -> { arrayOf() } Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> { @@ -168,6 +184,7 @@ class NetworkSensorManager : SensorManager { override fun requestSensorUpdate( context: Context ) { + updateHotspotEnabledSensor(context) updateWifiConnectionSensor(context) updateBSSIDSensor(context) updateWifiIPSensor(context) @@ -184,6 +201,39 @@ class NetworkSensorManager : SensorManager { private fun hasWifi(context: Context): Boolean = context.applicationContext.getSystemService() != null + @SuppressLint("PrivateApi") + private fun hasHotspot(context: Context): Boolean { + // Watch doesn't have hotspot. + if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { + return false + } + val wifiManager: WifiManager = context.applicationContext.getSystemService()!! + return try { + wifiManager.javaClass.getDeclaredMethod("isWifiApEnabled") + true + } catch (e: NoSuchMethodException) { + false + } + } + private fun updateHotspotEnabledSensor(context: Context) { + if (!isEnabled(context, hotspotState)) { + return + } + val wifiManager: WifiManager = context.getSystemService()!! + + @SuppressLint("PrivateApi") + val method: Method = wifiManager.javaClass.getDeclaredMethod("isWifiApEnabled") + method.isAccessible = true + val enabled = method.invoke(wifiManager) as Boolean + val icon = if (enabled) "mdi:access-point" else "mdi:access-point-off" + onSensorUpdated( + context, + hotspotState, + enabled, + icon, + mapOf() + ) + } private fun updateWifiConnectionSensor(context: Context) { if (!isEnabled(context, wifiConnection) || !hasWifi(context)) { return diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 6d0f79fda..f393694a4 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -113,6 +113,7 @@ WiFi signal strength WiFi state WiFi connection + Hotspot state Binary sensor Unlock using your biometric or screen lock credential Confirm to continue @@ -590,6 +591,7 @@ Whether headphones are plugged into the device Whether the headset is currently in use Whether high accuracy mode is active on the device + Whether the WIFI hotspot is enabled Whether the device is in an interactive state Information about the total and available storage space internally Whether the keyguard is currently locked