Activity recognition sensor added (#784)

* Activity recognition sensor added

* Added all probable activities with their confidence

* Corrected update interval

* ktlint
This commit is contained in:
Oleksandr Kapshuk 2020-08-25 17:52:58 +03:00 committed by GitHub
parent c50c0adf0e
commit bd3b58c007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 271 additions and 0 deletions

View file

@ -0,0 +1,197 @@
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.os.Build
import android.util.Log
import com.google.android.gms.location.ActivityRecognition
import com.google.android.gms.location.ActivityRecognitionResult
import com.google.android.gms.location.DetectedActivity
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.integration.SensorRegistration
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ActivitySensorManager : BroadcastReceiver(), SensorManager {
companion object {
internal const val TAG = "ActivitySM"
const val ACTION_REQUEST_ACTIVITY_UPDATES =
"io.homeassistant.companion.android.background.REQUEST_ACTIVITY_UPDATES"
const val ACTION_UPDATE_ACTIVITY =
"io.homeassistant.companion.android.background.UPDATE_ACTIVITY"
fun restartActivityTracking(context: Context) {
val intent = Intent(context, ActivitySensorManager::class.java)
intent.action = ACTION_REQUEST_ACTIVITY_UPDATES
context.sendBroadcast(intent)
}
private val activity = SensorManager.BasicSensor(
"detected_activity",
"sensor",
"Detected Activity"
)
private var stored_activity: String = "unknown"
private var stored_attributes = mutableMapOf<String, Int>()
}
@Inject
lateinit var integrationUseCase: IntegrationUseCase
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
override fun onReceive(context: Context, intent: Intent) {
ensureInjected(context)
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED,
ACTION_REQUEST_ACTIVITY_UPDATES -> setupActivityTracking(context)
ACTION_UPDATE_ACTIVITY -> handleActivityUpdate(intent, context)
else -> Log.w(TAG, "Unknown intent action: ${intent.action}!")
}
}
private fun ensureInjected(context: Context) {
if (context.applicationContext is GraphComponentAccessor) {
DaggerSensorComponent.builder()
.appComponent((context.applicationContext as GraphComponentAccessor).appComponent)
.build()
.inject(this)
} else {
throw Exception("Application Context passed is not of our application!")
}
}
private fun setupActivityTracking(context: Context) {
if (!checkPermission(context)) {
Log.w(TAG, "Not starting activity tracking because of permissions.")
return
}
val sensorDao = AppDatabase.getInstance(context).sensorDao()
ioScope.launch {
try {
Log.d(TAG, "Unregistering for activity updates.")
ActivityRecognition.getClient(context).removeActivityUpdates(getPendingIntent(context))
if (sensorDao.get(activity.id)?.enabled == true) {
Log.d(TAG, "Registering for activity updates.")
ActivityRecognition.getClient(context).requestActivityUpdates(120000, getPendingIntent(context))
}
} catch (e: Exception) {
Log.e(TAG, "Issue setting up activity tracking", e)
}
}
}
private fun getPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, ActivitySensorManager::class.java)
intent.action = ACTION_UPDATE_ACTIVITY
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private fun handleActivityUpdate(intent: Intent, context: Context) {
Log.d(TAG, "Received activity update.")
if (ActivityRecognitionResult.hasResult(intent)) {
val result = ActivityRecognitionResult.extractResult(intent)
var probActivity = typeToString(result.mostProbableActivity)
if (probActivity == "on_foot")
probActivity = getSubActivity(result)
if (stored_activity != probActivity) {
stored_activity = probActivity
stored_attributes.clear()
for (act in result.probableActivities)
stored_attributes[typeToString(act)] = act.confidence
val intent = Intent(context, SensorReceiver::class.java)
intent.action = SensorReceiver.ACTION_REQUEST_SENSORS_UPDATE
context.sendBroadcast(intent)
}
}
}
private fun typeToString(activity: DetectedActivity): String {
return when (activity.type) {
DetectedActivity.IN_VEHICLE -> "in_vehicle"
DetectedActivity.ON_BICYCLE -> "on_bicycle"
DetectedActivity.ON_FOOT -> "on_foot"
DetectedActivity.RUNNING -> "running"
DetectedActivity.STILL -> "still"
DetectedActivity.TILTING -> "tilting"
DetectedActivity.WALKING -> "walking"
DetectedActivity.UNKNOWN -> "unknown"
else -> "unknown"
}
}
private fun getSubActivity(result: ActivityRecognitionResult): String {
if (result.probableActivities[1].type == DetectedActivity.RUNNING) return "running"
if (result.probableActivities[1].type == DetectedActivity.WALKING) return "walking"
return "on_foot"
}
override val name: String
get() = "Activity Sensors"
override val availableSensors: List<SensorManager.BasicSensor>
get() = listOf(activity)
override fun requiredPermissions(): Array<String> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(
Manifest.permission.ACTIVITY_RECOGNITION
)
} else {
arrayOf()
}
}
override fun getSensorData(
context: Context,
sensorId: String
): SensorRegistration<Any> {
return when (sensorId) {
activity.id -> getActivitySensor(context)
else -> throw IllegalArgumentException("Unknown sensorId: $sensorId")
}
}
private fun getActivitySensor(context: Context): SensorRegistration<Any> {
return activity.toSensorRegistration(
stored_activity,
getSensorIcon(stored_activity),
stored_attributes
)
}
private fun getSensorIcon(activity: String): String {
return when (activity) {
"in_vehicle" -> "mdi:car"
"on_bicycle" -> "mdi:bike"
"on_foot" -> "mdi:shoe-print"
"still" -> "mdi:sleep"
"tilting" -> "mdi:phone-rotate-portrait"
"walking" -> "mdi:walk"
"running" -> "mdi:run"
else -> "mdi:progress-question"
}
}
}

View file

@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.hardware.telephony" android:required="false"/>
@ -76,6 +77,13 @@
<receiver android:name=".widgets.template.TemplateWidget" android:label="Template Widget">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
<action android:name="io.homeassistant.companion.android.background.REQUEST_SENSORS_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="io.homeassistant.companion.android.widgets.template.TemplateWidget.RECEIVE_DATA" />
</intent-filter>
@ -112,6 +120,15 @@
</intent-filter>
</receiver>
<receiver
android:name=".sensors.ActivitySensorManager"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="io.homeassistant.companion.android.background.REQUEST_ACTIVITY_UPDATES" />
</intent-filter>
</receiver>
<activity android:name=".launch.LaunchActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -16,4 +16,6 @@ interface SensorComponent {
fun inject(sensorsSettingsFragment: SensorsSettingsFragment)
fun inject(sensorDetailFragment: SensorDetailFragment)
fun inject(activitySensorManager: ActivitySensorManager)
}

View file

@ -20,6 +20,7 @@ class SensorReceiver : BroadcastReceiver() {
companion object {
const val TAG = "SensorReceiver"
val MANAGERS = listOf(
ActivitySensorManager(),
BatterySensorManager(),
BluetoothSensorManager(),
GeocodeSensorManager(),
@ -31,6 +32,9 @@ class SensorReceiver : BroadcastReceiver() {
StepsSensorManager(),
StorageSensorManager()
)
const val ACTION_REQUEST_SENSORS_UPDATE =
"io.homeassistant.companion.android.background.REQUEST_SENSORS_UPDATE"
}
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO + Job())
@ -53,6 +57,7 @@ class SensorReceiver : BroadcastReceiver() {
.inject(this)
LocationBroadcastReceiver.restartLocationTracking(context)
ActivitySensorManager.restartActivityTracking(context)
ioScope.launch {
updateSensors(context, integrationUseCase)

View file

@ -0,0 +1,50 @@
package io.homeassistant.companion.android.sensors
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import io.homeassistant.companion.android.domain.integration.SensorRegistration
class ActivitySensorManager : BroadcastReceiver(), SensorManager {
companion object {
internal const val TAG = "ActivitySM"
const val ACTION_REQUEST_ACTIVITY_UPDATES =
"io.homeassistant.companion.android.background.REQUEST_ACTIVITY_UPDATES"
const val ACTION_UPDATE_ACTIVITY =
"io.homeassistant.companion.android.background.UPDATE_ACTIVITY"
fun restartActivityTracking(context: Context) {
// Noop
}
private val activity = SensorManager.BasicSensor(
"detected_activity",
"sensor",
"Detected Activity"
)
private var last_activity: String = "unknown"
}
override fun onReceive(context: Context, intent: Intent) {
// Noop
}
override val name: String
get() = "Activity Sensors"
override val availableSensors: List<SensorManager.BasicSensor>
get() = listOf()
override fun requiredPermissions(): Array<String> {
// Noop
return emptyArray()
}
override fun getSensorData(context: Context, sensorId: String): SensorRegistration<Any> {
TODO("Not yet implemented")
}
}