Incoming/Outgoing phone number tracking (#978)

This commit is contained in:
Oleksandr Kapshuk 2020-10-02 00:51:22 +03:00 committed by GitHub
parent 41bacb346a
commit 09a4c5c563
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 45 deletions

View file

@ -15,6 +15,7 @@
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

View file

@ -2,6 +2,7 @@ package io.homeassistant.companion.android.sensors
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.telephony.SubscriptionInfo import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
@ -19,6 +20,13 @@ class PhoneStateSensorManager : SensorManager {
R.string.sensor_description_phone_state R.string.sensor_description_phone_state
) )
val callNumber = SensorManager.BasicSensor(
"call_number",
"sensor",
R.string.basic_sensor_name_call_number,
R.string.sensor_description_call_number
)
val sim_1 = SensorManager.BasicSensor( val sim_1 = SensorManager.BasicSensor(
"sim_1", "sim_1",
"sensor", "sensor",
@ -41,49 +49,77 @@ class PhoneStateSensorManager : SensorManager {
override val availableSensors: List<SensorManager.BasicSensor> override val availableSensors: List<SensorManager.BasicSensor>
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1)
listOf(phoneState, sim_1, sim_2) listOf(phoneState, callNumber, sim_1, sim_2)
else listOf(phoneState) else listOf(phoneState, callNumber)
override fun requiredPermissions(sensorId: String): Array<String> { override fun requiredPermissions(sensorId: String): Array<String> {
return arrayOf(Manifest.permission.READ_PHONE_STATE) return if (sensorId == callNumber.id) {
arrayOf(Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CALL_LOG)
} else arrayOf(Manifest.permission.READ_PHONE_STATE)
} }
override fun requestSensorUpdate( override fun requestSensorUpdate(
context: Context context: Context
) { ) {
updatePhoneStateSensor(context) checkPhoneState(context)
updateSimSensor(context, 0) updateSimSensor(context, 0)
updateSimSensor(context, 1) updateSimSensor(context, 1)
} }
private fun updatePhoneStateSensor(context: Context) { private fun checkPhoneState(context: Context) {
if (!isEnabled(context, phoneState.id)) if (isEnabled(context, phoneState.id) || isEnabled(context, callNumber.id)) {
return var currentPhoneState = "unknown"
var currentPhoneState = "unavailable"
if (checkPermission(context, phoneState.id)) {
val telephonyManager =
(context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
currentPhoneState = when (telephonyManager.callState) { if (checkPermission(context, phoneState.id)) {
0 -> "idle" val telephonyManager =
1 -> "ringing" (context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
2 -> "offhook"
else -> "unknown" currentPhoneState = when (telephonyManager.callState) {
0 -> "idle"
1 -> "ringing"
2 -> "offhook"
else -> "unknown"
}
} }
} if (isEnabled(context, phoneState.id))
updatePhoneStateSensor(context, currentPhoneState)
if (isEnabled(context, callNumber.id) && currentPhoneState in arrayOf("idle", "unknown"))
updateCallNumberSensor(context, "none")
}
}
private fun updatePhoneStateSensor(context: Context, state: String) {
var phoneIcon = "mdi:phone" var phoneIcon = "mdi:phone"
if (currentPhoneState == "ringing" || currentPhoneState == "offhook") if (state == "ringing" || state == "offhook")
phoneIcon += "-in-talk" phoneIcon += "-in-talk"
onSensorUpdated(context, onSensorUpdated(context,
phoneState, phoneState,
currentPhoneState, state,
phoneIcon, phoneIcon,
mapOf() mapOf()
) )
} }
fun updateCallNumber(context: Context, intent: Intent) {
if (checkPermission(context, callNumber.id)) {
if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {
val number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
updateCallNumberSensor(context, number)
}
}
}
private fun updateCallNumberSensor(context: Context, number: String) {
onSensorUpdated(context,
callNumber,
number,
"mdi:numeric",
mapOf()
)
}
private fun updateSimSensor(context: Context, slotIndex: Int) { private fun updateSimSensor(context: Context, slotIndex: Int) {
val basicSimSensor = when (slotIndex) { val basicSimSensor = when (slotIndex) {
0 -> sim_1 0 -> sim_1
@ -110,7 +146,7 @@ class PhoneStateSensorManager : SensorManager {
attrs["mcc"] = info.mccString.toString() attrs["mcc"] = info.mccString.toString()
attrs["mnc"] = info.mncString.toString() attrs["mnc"] = info.mncString.toString()
attrs["is opportunistic"] = info.isOpportunistic attrs["is opportunistic"] = info.isOpportunistic
if (info.dataRoaming == SubscriptionManager.DATA_ROAMING_ENABLE) attrs["data roaming"] = "enable" if (info.dataRoaming == SubscriptionManager.DATA_ROAMING_ENABLE) attrs["data roaming"] = "enable"
else attrs["data roaming"] = "disable" else attrs["data roaming"] = "disable"
} }
} }

View file

@ -4,6 +4,7 @@ import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
@ -57,6 +58,13 @@ class SensorReceiver : BroadcastReceiver() {
Intent.ACTION_POWER_DISCONNECTED Intent.ACTION_POWER_DISCONNECTED
) )
private 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,
BluetoothAdapter.ACTION_STATE_CHANGED to BluetoothSensorManager.bluetoothState.id
)
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
DaggerSensorComponent.builder() DaggerSensorComponent.builder()
@ -64,34 +72,20 @@ class SensorReceiver : BroadcastReceiver() {
.build() .build()
.inject(this) .inject(this)
when (intent.action) { if (skippableActions.containsKey(intent.action)) {
"android.app.action.NEXT_ALARM_CLOCK_CHANGED" -> { val sensor = skippableActions[intent.action]
val sensorDao = AppDatabase.getInstance(context).sensorDao() if (!isSensorEnabled(context, sensor!!)) {
val sensor = sensorDao.get(NextAlarmManager.nextAlarm.id) Log.d(TAG, String.format
if (sensor?.enabled != true) { ("Sensor %s corresponding to received event %s is disabled, skipping sensors update", sensor, intent.action))
Log.d(TAG, "Alarm Sensor disabled, skipping sensors update") return
return
}
}
"android.bluetooth.device.action.ACL_CONNECTED",
"android.bluetooth.device.action.ACL_DISCONNECTED" -> {
val sensorDao = AppDatabase.getInstance(context).sensorDao()
val sensorBtConn = sensorDao.get(BluetoothSensorManager.bluetoothConnection.id)
if (sensorBtConn?.enabled != true) {
Log.d(TAG, "Bluetooth Connection Sensor disabled, skipping sensors update")
return
}
}
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val sensorDao = AppDatabase.getInstance(context).sensorDao()
val sensorBtState = sensorDao.get(BluetoothSensorManager.bluetoothState.id)
if (sensorBtState?.enabled != true) {
Log.d(TAG, "Bluetooth State Sensor disabled, skipping sensors update")
return
}
} }
} }
if (intent.action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED) &&
isSensorEnabled(context, PhoneStateSensorManager.callNumber.id)) {
PhoneStateSensorManager().updateCallNumber(context, intent)
}
ioScope.launch { ioScope.launch {
updateSensors(context, integrationUseCase) updateSensors(context, integrationUseCase)
if (chargingActions.contains(intent.action)) { if (chargingActions.contains(intent.action)) {
@ -103,6 +97,10 @@ class SensorReceiver : BroadcastReceiver() {
} }
} }
private fun isSensorEnabled(context: Context, id: String): Boolean {
return AppDatabase.getInstance(context).sensorDao().get(id)?.enabled == true
}
suspend fun updateSensors( suspend fun updateSensors(
context: Context, context: Context,
integrationUseCase: IntegrationRepository integrationUseCase: IntegrationRepository

View file

@ -28,6 +28,7 @@
<string name="basic_sensor_name_battery_state">Battery State</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">Bluetooth Connection</string>
<string name="basic_sensor_name_bluetooth_state">Bluetooth State</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_charger_type">Charger Type</string>
<string name="basic_sensor_name_charging">Is Charging</string> <string name="basic_sensor_name_charging">Is Charging</string>
<string name="basic_sensor_name_doze">Doze Mode</string> <string name="basic_sensor_name_doze">Doze Mode</string>
@ -185,6 +186,7 @@ like to connect to:</string>
<string name="sensor_description_battery_state">The current charging state of the battery</string> <string name="sensor_description_battery_state">The current charging state of the battery</string>
<string name="sensor_description_bluetooth_connection">Information about currently connected bluetooth devices</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_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_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_charging">Whether or not the device is actively charging</string>
<string name="sensor_description_detected_activity">The current activity type as computed by Googles Activity Recognition API</string> <string name="sensor_description_detected_activity">The current activity type as computed by Googles Activity Recognition API</string>