diff --git a/app/src/full/java/io/homeassistant/companion/android/notifications/MessagingService.kt b/app/src/full/java/io/homeassistant/companion/android/notifications/MessagingService.kt index d4d9b76a8..466e3dd7f 100644 --- a/app/src/full/java/io/homeassistant/companion/android/notifications/MessagingService.kt +++ b/app/src/full/java/io/homeassistant/companion/android/notifications/MessagingService.kt @@ -35,6 +35,8 @@ import io.homeassistant.companion.android.common.data.authentication.SessionStat import io.homeassistant.companion.android.common.data.integration.DeviceRegistration import io.homeassistant.companion.android.common.data.integration.IntegrationRepository import io.homeassistant.companion.android.common.data.url.UrlRepository +import io.homeassistant.companion.android.database.AppDatabase +import io.homeassistant.companion.android.database.notification.NotificationItem import io.homeassistant.companion.android.sensors.LocationSensorManager import io.homeassistant.companion.android.util.UrlHandler import io.homeassistant.companion.android.util.cancel @@ -114,6 +116,10 @@ class MessagingService : FirebaseMessagingService() { // Check if message contains a data payload. remoteMessage.data.let { Log.d(TAG, "Message data payload: " + remoteMessage.data) + val notificationDao = AppDatabase.getInstance(applicationContext).notificationDao() + val now = System.currentTimeMillis() + val notificationRow = NotificationItem(0, now, it[MESSAGE].toString(), it.toString()) + notificationDao.add(notificationRow) when { it[MESSAGE] == REQUEST_LOCATION_UPDATE -> { diff --git a/app/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt b/app/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt index 6571acd67..2040ebccb 100644 --- a/app/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt +++ b/app/src/main/java/io/homeassistant/companion/android/database/AppDatabase.kt @@ -19,6 +19,8 @@ import io.homeassistant.companion.android.R import io.homeassistant.companion.android.common.data.integration.IntegrationRepository import io.homeassistant.companion.android.database.authentication.Authentication import io.homeassistant.companion.android.database.authentication.AuthenticationDao +import io.homeassistant.companion.android.database.notification.NotificationDao +import io.homeassistant.companion.android.database.notification.NotificationItem import io.homeassistant.companion.android.database.sensor.Attribute import io.homeassistant.companion.android.database.sensor.Sensor import io.homeassistant.companion.android.database.sensor.SensorDao @@ -42,9 +44,10 @@ import kotlinx.coroutines.runBlocking ButtonWidgetEntity::class, MediaPlayerControlsWidgetEntity::class, StaticWidgetEntity::class, - TemplateWidgetEntity::class + TemplateWidgetEntity::class, + NotificationItem::class ], - version = 12, + version = 13, exportSchema = false ) abstract class AppDatabase : RoomDatabase() { @@ -54,6 +57,7 @@ abstract class AppDatabase : RoomDatabase() { abstract fun mediaPlayCtrlWidgetDao(): MediaPlayerControlsWidgetDao abstract fun staticWidgetDao(): StaticWidgetDao abstract fun templateWidgetDao(): TemplateWidgetDao + abstract fun notificationDao(): NotificationDao companion object { private const val DATABASE_NAME = "HomeAssistantDB" @@ -89,7 +93,8 @@ abstract class AppDatabase : RoomDatabase() { MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, - MIGRATION_11_12 + MIGRATION_11_12, + MIGRATION_12_13 ) .fallbackToDestructiveMigration() .build() @@ -266,6 +271,12 @@ abstract class AppDatabase : RoomDatabase() { } } + private val MIGRATION_12_13 = object : Migration(12, 13) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `notification_history` (`id` INTEGER NOT NULL, `received` LONG NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, PRIMARY KEY(`id`))") + } + } + private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager diff --git a/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationDao.kt b/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationDao.kt new file mode 100644 index 000000000..4cc2a0c7a --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationDao.kt @@ -0,0 +1,28 @@ +package io.homeassistant.companion.android.database.notification + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +interface NotificationDao { + + @Query("SELECT * FROM notification_history WHERE id = :id") + fun get(id: Int): NotificationItem? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun add(notification: NotificationItem) + + @Query("SELECT * FROM notification_history ORDER BY received DESC") + fun getAll(): Array? + + @Query("SELECT * FROM notification_history ORDER BY received DESC LIMIT 25") + fun getLast25(): Array? + + @Query("DELETE FROM notification_history WHERE id = :id") + fun delete(id: Int) + + @Query("DELETE FROM notification_history") + fun deleteAll() +} diff --git a/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationItem.kt b/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationItem.kt new file mode 100644 index 000000000..5af0a2cdf --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/database/notification/NotificationItem.kt @@ -0,0 +1,17 @@ +package io.homeassistant.companion.android.database.notification + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "notification_history") +data class NotificationItem( + @PrimaryKey(autoGenerate = true) + val id: Int, + @ColumnInfo(name = "received") + val received: Long, + @ColumnInfo(name = "message") + val message: String, + @ColumnInfo(name = "data") + val data: String +) diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/SettingsFragment.kt b/app/src/main/java/io/homeassistant/companion/android/settings/SettingsFragment.kt index b6739930d..f4c19c67e 100644 --- a/app/src/main/java/io/homeassistant/companion/android/settings/SettingsFragment.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/SettingsFragment.kt @@ -14,6 +14,7 @@ import androidx.biometric.BiometricManager import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import io.homeassistant.companion.android.BuildConfig @@ -24,6 +25,7 @@ import io.homeassistant.companion.android.authenticator.Authenticator import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor import io.homeassistant.companion.android.nfc.NfcSetupActivity import io.homeassistant.companion.android.sensors.SensorsSettingsFragment +import io.homeassistant.companion.android.settings.notification.NotificationHistoryFragment import io.homeassistant.companion.android.settings.ssid.SsidDialogFragment import io.homeassistant.companion.android.settings.ssid.SsidPreference import javax.inject.Inject @@ -128,6 +130,21 @@ class SettingsFragment : PreferenceFragmentCompat(), SettingsView { } if (BuildConfig.FLAVOR == "full") { + findPreference("notifications")?.let { + it.isVisible = true + } + findPreference("notification_history")?.let { + it.isVisible = true + it.setOnPreferenceClickListener { + parentFragmentManager + .beginTransaction() + .replace(R.id.content, NotificationHistoryFragment.newInstance()) + .addToBackStack(getString(R.string.notifications)) + .commit() + return@setOnPreferenceClickListener true + } + } + findPreference("notification_rate_limit")?.let { val rateLimits = presenter.getNotificationRateLimits() diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationDetailFragment.kt b/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationDetailFragment.kt new file mode 100644 index 000000000..a40b0388b --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationDetailFragment.kt @@ -0,0 +1,41 @@ +package io.homeassistant.companion.android.settings.notification + +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import io.homeassistant.companion.android.R +import io.homeassistant.companion.android.database.notification.NotificationItem +import java.util.Calendar +import java.util.GregorianCalendar + +class NotificationDetailFragment( + private val notification: NotificationItem +) : + PreferenceFragmentCompat() { + + companion object { + fun newInstance( + notification: NotificationItem + ): NotificationDetailFragment { + return NotificationDetailFragment(notification) + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + + addPreferencesFromResource(R.xml.notification_detail) + findPreference("received_at")?.let { + val cal: Calendar = GregorianCalendar() + cal.timeInMillis = notification.received + it.summary = cal.time.toString() + } + + findPreference("message")?.let { + it.summary = notification.message + } + + findPreference("data")?.let { + it.summary = notification.data + } + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationHistoryFragment.kt b/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationHistoryFragment.kt new file mode 100644 index 000000000..63368a2b0 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/settings/notification/NotificationHistoryFragment.kt @@ -0,0 +1,62 @@ +package io.homeassistant.companion.android.settings.notification + +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import io.homeassistant.companion.android.R +import io.homeassistant.companion.android.database.AppDatabase +import java.util.Calendar +import java.util.GregorianCalendar + +class NotificationHistoryFragment : PreferenceFragmentCompat() { + + companion object { + fun newInstance(): NotificationHistoryFragment { + return NotificationHistoryFragment() + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + + setPreferencesFromResource(R.xml.notifications, rootKey) + val notificationDao = AppDatabase.getInstance(requireContext()).notificationDao() + val notificationList = notificationDao.getLast25() + + val prefCategory = PreferenceCategory(preferenceScreen.context) + if (!notificationList.isNullOrEmpty()) { + prefCategory.title = requireContext().getString(R.string.last_25_notifications) + prefCategory.isIconSpaceReserved = false + preferenceScreen.addPreference(prefCategory) + for (item in notificationList) { + val pref = Preference(preferenceScreen.context) + val cal: Calendar = GregorianCalendar() + cal.timeInMillis = item.received + pref.key = item.id.toString() + pref.title = cal.time.toString() + pref.summary = item.message + pref.isIconSpaceReserved = false + + pref.setOnPreferenceClickListener { + parentFragmentManager + .beginTransaction() + .replace( + R.id.content, + NotificationDetailFragment.newInstance( + item + ) + ) + .addToBackStack("Notification Detail") + .commit() + return@setOnPreferenceClickListener true + } + + prefCategory.addPreference(pref) + } + } else { + findPreference("no_notifications")?.let { + it.isVisible = true + } + } + } +} diff --git a/app/src/main/res/drawable/ic_notification_history.xml b/app/src/main/res/drawable/ic_notification_history.xml new file mode 100644 index 000000000..88a71b0fa --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_history.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a090180c..39693590c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -162,8 +162,17 @@ Home Assistant instance Successfully wrote nfc tag Please fill out the form first Your connection to this site is not private. + Notifications Failed to send event on notification cleared Failed to send event on notification dismissed + Full Notification Data + Notification History + History of Notifications (currently displays the last 25 notifications received) + Message + Notification Received At + Last 25 Notifications + No Notifications + You have not received any notifications yet Other Settings Password In order to use location tracking features or different connection urls based on WiFi SSID we need access to your location. If you want consistent background updates you will also need to allow background processing diff --git a/app/src/main/res/xml/notification_detail.xml b/app/src/main/res/xml/notification_detail.xml new file mode 100644 index 000000000..8ae8cfddb --- /dev/null +++ b/app/src/main/res/xml/notification_detail.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/notifications.xml b/app/src/main/res/xml/notifications.xml new file mode 100644 index 000000000..198c7e44e --- /dev/null +++ b/app/src/main/res/xml/notifications.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 74e71a99d..aaa9b454b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -86,7 +86,15 @@ + android:title="@string/notifications" + android:key="notifications" + app:isPreferenceVisible="false"> + + +