Last Notification Sensor (#1018)

* Initial work on last notification sensor.

* Add allow list for packages.

* Add way to get sensor setting in common place.

* ktLint

* Add isEnabled check.
This commit is contained in:
Justin Bassett 2020-10-08 13:30:26 -04:00 committed by GitHub
parent 3c0f1a8dce
commit 989229f2d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 1 deletions

View file

@ -113,6 +113,13 @@
</intent-filter>
</activity>
<service android:name=".sensors.NotificationSensorManager"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
<receiver
android:name=".sensors.LocationSensorManager"
android:enabled="true"

View file

@ -0,0 +1,78 @@
package io.homeassistant.companion.android.sensors
import android.Manifest
import android.content.Context
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationManagerCompat
import io.homeassistant.companion.android.R
class NotificationSensorManager : NotificationListenerService(), SensorManager {
companion object {
val lastNotification = SensorManager.BasicSensor(
"last_notification",
"sensor",
R.string.basic_sensor_name_last_notification,
R.string.sensor_description_last_notification
)
}
override val name: Int
get() = R.string.sensor_name_last_notification
override val availableSensors: List<SensorManager.BasicSensor>
get() = listOf(lastNotification)
override val enabledByDefault: Boolean
get() = false
override fun requiredPermissions(sensorId: String): Array<String> {
return arrayOf(Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
}
override fun checkPermission(context: Context, sensorId: String): Boolean {
return NotificationManagerCompat
.getEnabledListenerPackages(context)
.contains(context.packageName)
}
override fun requestSensorUpdate(context: Context) {
// Noop
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
super.onNotificationPosted(sbn)
if (!isEnabled(applicationContext, lastNotification.id))
return
val allowPackages = getSetting(
applicationContext,
lastNotification,
"Allow List",
"list-apps",
""
).split(", ").filter { it.isNotBlank() }
if (sbn.packageName == application.packageName ||
(allowPackages.isNotEmpty() && sbn.packageName !in allowPackages)) {
return
}
val attr = sbn.notification.extras.keySet()
.map { it to sbn.notification.extras.get(it) }
.toMap()
.plus("package" to sbn.packageName)
// Attempt to use the text of the notification but fallback to package name if all else fails.
val state = attr["android.text"] ?: attr["android.title"] ?: sbn.packageName
onSensorUpdated(
applicationContext,
lastNotification,
state.toString(),
"mdi:bell-ring",
attr
)
// Need to send update!
SensorWorker.start(applicationContext)
}
}

View file

@ -1,8 +1,11 @@
package io.homeassistant.companion.android.sensors
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import android.text.InputType
import androidx.preference.EditTextPreference
import androidx.preference.MultiSelectListPreference
@ -67,7 +70,12 @@ class SensorDetailFragment(
val isEnabled = newState as Boolean
if (isEnabled && !sensorManager.checkPermission(requireContext(), basicSensor.id)) {
requestPermissions(sensorManager.requiredPermissions(basicSensor.id), 0)
val permissions = sensorManager.requiredPermissions(basicSensor.id)
when {
permissions.any { perm -> perm == Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE } ->
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
else -> requestPermissions(permissions, 0)
}
return@setOnPreferenceChangeListener false
}

View file

@ -8,6 +8,7 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.sensor.Attribute
import io.homeassistant.companion.android.database.sensor.Sensor
import io.homeassistant.companion.android.database.sensor.Setting
interface SensorManager {
@ -58,6 +59,24 @@ interface SensorManager {
return true
}
fun getSetting(
context: Context,
sensor: BasicSensor,
settingName: String,
settingType: String,
default: String
): String {
val sensorDao = AppDatabase.getInstance(context).sensorDao()
val setting = sensorDao
.getSettings(sensor.id)
.firstOrNull { it.name == settingName }
?.value
if (setting == null)
sensorDao.add(Setting(sensor.id, settingName, default, settingType))
return setting ?: default
}
fun onSensorUpdated(
context: Context,
basicSensor: BasicSensor,

View file

@ -34,6 +34,7 @@ class SensorReceiver : BroadcastReceiver() {
LocationSensorManager(),
NetworkSensorManager(),
NextAlarmManager(),
NotificationSensorManager(),
PhoneStateSensorManager(),
PowerSensorManager(),
PressureSensorManager(),

View file

@ -40,6 +40,7 @@
<string name="basic_sensor_name_geolocation">Geocoded Location</string>
<string name="basic_sensor_name_interactive">Interactive</string>
<string name="basic_sensor_name_internal_storage">Internal Storage</string>
<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_location_accurate">Single Accurate Location</string>
<string name="basic_sensor_name_location_background">Background Location</string>
@ -217,6 +218,7 @@ like to connect to:</string>
<string name="sensor_description_headphone">Whether or not headphones are plugged into the device</string>
<string name="sensor_description_interactive">Whether or not the device is in an interactive state</string>
<string name="sensor_description_internal_storage">Information about the total and available storage space internally</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_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.</string>
@ -261,6 +263,7 @@ like to connect to:</string>
<string name="sensor_name_dnd">Do Not Disturb Sensor</string>
<string name="sensor_name_geolocation">Geolocation Sensors</string>
<string name="sensor_name_headphone">Headphones</string>
<string name="sensor_name_last_notification">Notification Sensors</string>
<string name="sensor_name_last_reboot">Last Reboot Sensor</string>
<string name="sensor_name_light">Light Sensor</string>
<string name="sensor_name_location">Location Sensors</string>