From 7d6f2d07e56c821891a453eb98f66c054b09e23e Mon Sep 17 00:00:00 2001 From: Julian Raffeiner Date: Wed, 18 Jun 2025 07:15:22 +0200 Subject: [PATCH 1/2] added override volume setting --- .../main/java/io/heckel/ntfy/db/Repository.kt | 43 ++++++++++++ .../io/heckel/ntfy/msg/NotificationService.kt | 13 ++-- .../io/heckel/ntfy/ui/SettingsActivity.kt | 69 ++++++++++++++++++- app/src/main/res/values/strings.xml | 5 ++ app/src/main/res/values/values.xml | 3 + app/src/main/res/xml/main_preferences.xml | 22 +++++- 6 files changed, 148 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 71b030920..52a26b351 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -7,6 +7,7 @@ import android.os.Build import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatDelegate import androidx.lifecycle.* +import io.heckel.ntfy.ui.SettingsActivity import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.validUrl import java.util.concurrent.ConcurrentHashMap @@ -302,6 +303,37 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas .apply() } + fun getOverrideVolumeMaxPriorityEnabled(): Boolean { + return sharedPrefs.getBoolean(SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED, false) + } + + fun setOverrideVolumeMaxPriorityEnabled(enabled: Boolean) { + sharedPrefs.edit() + .putBoolean(SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED, enabled) + .apply() + } + + fun getOverrideVolumeSetting(): Int { + return sharedPrefs.getInt(SHARED_PREFS_OVERRIDE_VOLUME_SETTING, 7) + } + + fun setOverrideVolumeSetting(value: Int) { + sharedPrefs.edit() + .putInt(SHARED_PREFS_OVERRIDE_VOLUME_SETTING, value) + .apply() + } + + fun getPreviousVolume(): Int { + return sharedPrefs.getInt(SHARED_PREFS_PREVIOUS_VOLUME, 7) + } + + fun setPreviousVolume(previousVolume: Int) { + sharedPrefs.edit() + .putInt(SHARED_PREFS_PREVIOUS_VOLUME, previousVolume) + .apply() + + } + fun getInsistentMaxPriorityEnabled(): Boolean { return sharedPrefs.getBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, false) // Disabled by default } @@ -475,6 +507,14 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas return connectionStatesLiveData.value!!.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE } } + fun registerOnSharedPreferenceChangeListener(settingsActivity: SettingsActivity) { + sharedPrefs.registerOnSharedPreferenceChangeListener(settingsActivity) + } + + fun unregisterOnSharedPreferenceChangeListener(settingsActivity: SettingsActivity) { + sharedPrefs.unregisterOnSharedPreferenceChangeListener(settingsActivity) + } + companion object { const val SHARED_PREFS_ID = "MainPreferences" const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion" @@ -489,6 +529,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled" const val SHARED_PREFS_UNIFIEDPUSH_ENABLED = "UnifiedPushEnabled" const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority" + const val SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED = "OverrideVolumeMaxPriority" + const val SHARED_PREFS_OVERRIDE_VOLUME_SETTING = "OverrideVolumeSetting" + const val SHARED_PREFS_PREVIOUS_VOLUME = "PreviousVolume" const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs" const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime" const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner) diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index eec1e59ba..08f970b1e 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -91,6 +91,7 @@ class NotificationService(val context: Context) { val title = formatTitle(subscription, notification) val groupId = if (subscription.dedicatedChannels) subscriptionGroupId(subscription) else DEFAULT_GROUP val channelId = toChannelId(groupId, notification.priority) + val overrrideVolume = notification.priority == PRIORITY_MAX && repository.getOverrideVolumeMaxPriorityEnabled() val insistent = notification.priority == PRIORITY_MAX && (repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED) val builder = NotificationCompat.Builder(context, channelId) @@ -112,7 +113,7 @@ class NotificationService(val context: Context) { maybeCreateNotificationGroup(groupId, subscriptionGroupName(subscription)) maybeCreateNotificationChannel(groupId, notification.priority) - maybePlayInsistentSound(groupId, insistent) + maybeOverrideVolumeAndPlayInsistentSound(groupId, overrrideVolume, insistent) notificationManager.notify(notification.notificationId, builder.build()) } @@ -440,19 +441,23 @@ class NotificationService(val context: Context) { } } - private fun maybePlayInsistentSound(groupId: String, insistent: Boolean) { - if (!insistent) { + private fun maybeOverrideVolumeAndPlayInsistentSound(groupId: String, overrideVolume: Boolean, insistent: Boolean) { + if (!overrideVolume) { return } try { val mediaPlayer = repository.mediaPlayer val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM) + audioManager.setStreamVolume(AudioManager.STREAM_ALARM, repository.getOverrideVolumeSetting(), 0) + repository.setPreviousVolume(previousVolume) if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { Log.d(TAG, "Media player: Playing insistent alarm on alarm channel") mediaPlayer.reset() mediaPlayer.setDataSource(context, getInsistentSound(groupId)) mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) - mediaPlayer.isLooping = true + mediaPlayer.isLooping = insistent + mediaPlayer.setOnCompletionListener { audioManager.setStreamVolume(AudioManager.STREAM_ALARM, repository.getPreviousVolume(), 0) } mediaPlayer.prepare() mediaPlayer.start() } else { diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index f4ef5d86a..b9db92ab8 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -6,7 +6,9 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.content.pm.PackageManager +import android.media.AudioManager import android.os.Build import android.os.Bundle import android.provider.Settings @@ -47,7 +49,7 @@ import java.util.concurrent.TimeUnit * https://github.com/googlearchive/android-preferences/blob/master/app/src/main/java/com/example/androidx/preference/sample/MainActivity.kt */ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, - UserFragment.UserDialogListener { + UserFragment.UserDialogListener, SharedPreferences.OnSharedPreferenceChangeListener { private lateinit var settingsFragment: SettingsFragment private lateinit var userSettingsFragment: UserSettingsFragment @@ -58,9 +60,11 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) + Log.d(TAG, "Create $this") repository = Repository.getInstance(this) + repository.registerOnSharedPreferenceChangeListener(this) serviceManager = SubscriberServiceManager(this) if (savedInstanceState == null) { @@ -83,6 +87,17 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere supportActionBar?.setDisplayHomeAsUpEnabled(true) } + override fun onDestroy() { + super.onDestroy() + repository.unregisterOnSharedPreferenceChangeListener(this) + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key.equals(Repository.SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED)){ + this.recreate() + } + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // Save current activity title, so we can set it again after a configuration change @@ -200,6 +215,45 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } + // Override Volume Setting for max priority + val overrideVolumeMaxPriorityPrefId = context?.getString(R.string.settings_notifications_override_volume_max_priority_key) ?: return + val overrideVolumeMaxPriority: SwitchPreference? = findPreference(overrideVolumeMaxPriorityPrefId) + overrideVolumeMaxPriority?.isChecked = repository.getOverrideVolumeMaxPriorityEnabled() + overrideVolumeMaxPriority?.preferenceDataStore = object : PreferenceDataStore() { + override fun putBoolean(key: String?, value: Boolean) { + repository.setOverrideVolumeMaxPriorityEnabled(value) + } + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + return repository.getOverrideVolumeMaxPriorityEnabled() + } + } + overrideVolumeMaxPriority?.summaryProvider = Preference.SummaryProvider { pref -> + if (pref.isChecked) { + getString(R.string.settings_notifications_override_volume_max_priority_summary_enabled) + } else { + getString(R.string.settings_notifications_override_volume_max_priority_summary_disabled) + } + } + + // Override Volume Level Setting + val overrideVolumeSettingPrefId = context?.getString(R.string.settings_notifications_override_volume_setting_key) ?: return + val overrideVolumeSetting: SeekBarPreference? = findPreference(overrideVolumeSettingPrefId) + overrideVolumeSetting?.value = repository.getOverrideVolumeSetting() + val audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as AudioManager + overrideVolumeSetting?.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM) + overrideVolumeSetting?.preferenceDataStore = object : PreferenceDataStore() { + override fun getInt(key: String?, defValue: Int): Int { + return repository.getOverrideVolumeSetting() + } + + override fun putInt(key: String?, value: Int) { + repository.setOverrideVolumeSetting(value) + } + } + overrideVolumeSetting?.summaryProvider = Preference.SummaryProvider { _ -> + getString(R.string.settings_notifications_override_volume_setting_summary) + } + // Keep alerting for max priority val insistentMaxPriorityPrefId = context?.getString(R.string.settings_notifications_insistent_max_priority_key) ?: return val insistentMaxPriority: SwitchPreference? = findPreference(insistentMaxPriorityPrefId) @@ -220,6 +274,19 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } + val overrideActive = repository.getOverrideVolumeMaxPriorityEnabled() + + if (!overrideActive) { + val notificationCategoryPrefId = context?.getString(R.string.settings_notifications_category_key) ?: return + val notificationCategory: PreferenceCategory? = findPreference(notificationCategoryPrefId) + if (overrideVolumeSetting != null) { + notificationCategory?.removePreference(overrideVolumeSetting) + } + if (insistentMaxPriority != null) { + notificationCategory?.removePreference(insistentMaxPriority) + } + } + // Channel settings val channelPrefsPrefId = context?.getString(R.string.settings_notifications_channel_prefs_key) ?: return val channelPrefs: Preference? = findPreference(channelPrefsPrefId) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c77599010..b00a3f795 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -281,6 +281,11 @@ After one week After one month After 3 months + Override Volume + Volume + Override Volume for highest priority + Override system volume for max priority notifications + Use system volume for max priority notifications Keep alerting for highest priority Max priority notifications continuously alert until dismissed Max priority notifications only alert once diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index f94a598b2..bdcf58d37 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -13,11 +13,14 @@ https://github.com/sponsors/binwiederhier + NotificationsCategory MutedUntil MinPriority ChannelPrefs AutoDownload AutoDelete + OverrideVolumeMaxPriority + OverrideVolumeSetting InsistentMaxPriority DefaultBaseURL ManageUsers diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index 0be2442aa..29d00f36b 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -1,6 +1,8 @@ - - + + + Date: Wed, 18 Jun 2025 15:16:23 +0200 Subject: [PATCH 2/2] added override volume setting to per channel settings --- .../io.heckel.ntfy.db.Database/13.json | 34 +- .../io.heckel.ntfy.db.Database/14.json | 368 ++++++++++++++++++ .../java/io/heckel/ntfy/backup/Backuper.kt | 6 + .../main/java/io/heckel/ntfy/db/Database.kt | 28 +- .../main/java/io/heckel/ntfy/db/Repository.kt | 9 + .../io/heckel/ntfy/msg/NotificationService.kt | 64 ++- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 2 + .../heckel/ntfy/ui/DetailSettingsActivity.kt | 52 +++ .../java/io/heckel/ntfy/ui/MainActivity.kt | 2 + .../io/heckel/ntfy/ui/SettingsActivity.kt | 23 +- .../io/heckel/ntfy/up/BroadcastReceiver.kt | 2 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/values.xml | 12 + app/src/main/res/xml/detail_preferences.xml | 26 +- app/src/main/res/xml/main_preferences.xml | 4 - 15 files changed, 575 insertions(+), 59 deletions(-) create mode 100644 app/schemas/io.heckel.ntfy.db.Database/14.json diff --git a/app/schemas/io.heckel.ntfy.db.Database/13.json b/app/schemas/io.heckel.ntfy.db.Database/13.json index 3a89d0f0b..e2b26ce1e 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/13.json +++ b/app/schemas/io.heckel.ntfy.db.Database/13.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 13, - "identityHash": "44fc291d937fdf02b9bc2d0abb10d2e0", + "identityHash": "17313a9fc219de273bef12b162805d49", "entities": [ { "tableName": "Subscription", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `insistent` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `insistent` INTEGER NOT NULL, `overrideVolumeMaxPriority` INTEGER NOT NULL, `overrideVolumeSetting` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -56,6 +56,18 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "overrideVolumeMaxPriority", + "columnName": "overrideVolumeMaxPriority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideVolumeSetting", + "columnName": "overrideVolumeSetting", + "affinity": "INTEGER", + "notNull": true + }, { "fieldPath": "lastNotificationId", "columnName": "lastNotificationId", @@ -94,10 +106,10 @@ } ], "primaryKey": { + "autoGenerate": false, "columnNames": [ "id" - ], - "autoGenerate": false + ] }, "indices": [ { @@ -255,11 +267,11 @@ } ], "primaryKey": { + "autoGenerate": false, "columnNames": [ "id", "subscriptionId" - ], - "autoGenerate": false + ] }, "indices": [], "foreignKeys": [] @@ -288,10 +300,10 @@ } ], "primaryKey": { + "autoGenerate": false, "columnNames": [ "baseUrl" - ], - "autoGenerate": false + ] }, "indices": [], "foreignKeys": [] @@ -338,10 +350,10 @@ } ], "primaryKey": { + "autoGenerate": true, "columnNames": [ "id" - ], - "autoGenerate": true + ] }, "indices": [], "foreignKeys": [] @@ -350,7 +362,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '44fc291d937fdf02b9bc2d0abb10d2e0')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '17313a9fc219de273bef12b162805d49')" ] } } \ No newline at end of file diff --git a/app/schemas/io.heckel.ntfy.db.Database/14.json b/app/schemas/io.heckel.ntfy.db.Database/14.json new file mode 100644 index 000000000..5f08e2825 --- /dev/null +++ b/app/schemas/io.heckel.ntfy.db.Database/14.json @@ -0,0 +1,368 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "17313a9fc219de273bef12b162805d49", + "entities": [ + { + "tableName": "Subscription", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `insistent` INTEGER NOT NULL, `overrideVolumeMaxPriority` INTEGER NOT NULL, `overrideVolumeSetting` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "instant", + "columnName": "instant", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mutedUntil", + "columnName": "mutedUntil", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minPriority", + "columnName": "minPriority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "autoDelete", + "columnName": "autoDelete", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insistent", + "columnName": "insistent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideVolumeMaxPriority", + "columnName": "overrideVolumeMaxPriority", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideVolumeSetting", + "columnName": "overrideVolumeSetting", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "upAppId", + "columnName": "upAppId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "upConnectorToken", + "columnName": "upConnectorToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dedicatedChannels", + "columnName": "dedicatedChannels", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Subscription_baseUrl_topic", + "unique": true, + "columnNames": [ + "baseUrl", + "topic" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)" + }, + { + "name": "index_Subscription_upConnectorToken", + "unique": true, + "columnNames": [ + "upConnectorToken" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subscriptionId", + "columnName": "subscriptionId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "encoding", + "columnName": "encoding", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationId", + "columnName": "notificationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "3" + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "click", + "columnName": "click", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actions", + "columnName": "actions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "icon.url", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "icon.contentUri", + "columnName": "icon_contentUri", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.name", + "columnName": "attachment_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.type", + "columnName": "attachment_type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.size", + "columnName": "attachment_size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachment.expires", + "columnName": "attachment_expires", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachment.url", + "columnName": "attachment_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.contentUri", + "columnName": "attachment_contentUri", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachment.progress", + "columnName": "attachment_progress", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "subscriptionId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "User", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))", + "fields": [ + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "baseUrl" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `tag` TEXT NOT NULL, `level` INTEGER NOT NULL, `message` TEXT NOT NULL, `exception` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "exception", + "columnName": "exception", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '17313a9fc219de273bef12b162805d49')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 89460c4bb..877b32a22 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -106,6 +106,8 @@ class Backuper(val context: Context) { minPriority = s.minPriority ?: Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = s.autoDelete ?: Repository.AUTO_DELETE_USE_GLOBAL, insistent = s.insistent ?: Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeMaxPriority = s.overrideVolumeMaxPriority ?: Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeSetting = s.overrideVolumeSetting ?: Repository.OVERRIDE_VOLUME_SETTING_DEFAULT, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -254,6 +256,8 @@ class Backuper(val context: Context) { minPriority = s.minPriority, autoDelete = s.autoDelete, insistent = s.insistent, + overrideVolumeMaxPriority = s.overrideVolumeMaxPriority, + overrideVolumeSetting = s.overrideVolumeSetting, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -373,6 +377,8 @@ data class Subscription( val minPriority: Int?, val autoDelete: Long?, val insistent: Int?, + val overrideVolumeMaxPriority: Int?, + val overrideVolumeSetting: Int?, val lastNotificationId: String?, val icon: String?, val upAppId: String?, diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 5f397969e..c95c4198b 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -19,6 +19,8 @@ data class Subscription( @ColumnInfo(name = "minPriority") val minPriority: Int, @ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds @ColumnInfo(name = "insistent") val insistent: Int, // Ring constantly for max priority notifications (-1 = use global, 0 = off, 1 = on) + @ColumnInfo(name = "overrideVolumeMaxPriority") val overrideVolumeMaxPriority: Int, // Override Volume for max priority notifications (-1 = use global, 0 = off, 1 = on) + @ColumnInfo(name = "overrideVolumeSetting") val overrideVolumeSetting: Int, @ColumnInfo(name = "lastNotificationId") val lastNotificationId: String?, // Used for polling, with since= @ColumnInfo(name = "icon") val icon: String?, // content://-URI (or later other identifier) @ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name @@ -39,6 +41,8 @@ data class Subscription( minPriority: Int, autoDelete: Long, insistent: Int, + overrideVolumeMaxPriority: Int, + overrideVolumeSetting: Int, lastNotificationId: String, icon: String, upAppId: String, @@ -55,6 +59,8 @@ data class Subscription( minPriority, autoDelete, insistent, + overrideVolumeMaxPriority, + overrideVolumeSetting, lastNotificationId, icon, upAppId, @@ -81,6 +87,8 @@ data class SubscriptionWithMetadata( val autoDelete: Long, val minPriority: Int, val insistent: Int, + val overrideVolumeMaxPriority: Int, + val overrideVolumeSetting: Int, val lastNotificationId: String?, val icon: String?, val upAppId: String?, @@ -196,7 +204,7 @@ data class LogEntry( this(0, timestamp, tag, level, message, exception) } -@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 13) +@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 14) @TypeConverters(Converters::class) abstract class Database : RoomDatabase() { abstract fun subscriptionDao(): SubscriptionDao @@ -224,6 +232,7 @@ abstract class Database : RoomDatabase() { .addMigrations(MIGRATION_10_11) .addMigrations(MIGRATION_11_12) .addMigrations(MIGRATION_12_13) + .addMigrations(MIGRATION_13_14) .fallbackToDestructiveMigration() .build() this.instance = instance @@ -329,6 +338,13 @@ abstract class Database : RoomDatabase() { db.execSQL("ALTER TABLE Subscription ADD COLUMN dedicatedChannels INTEGER NOT NULL DEFAULT (0)") } } + + private val MIGRATION_13_14 = object : Migration(13, 14) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Subscription ADD COLUMN overrideVolumeMaxPriority INTEGER NOT NULL DEFAULT (-1)") // = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL + db.execSQL("ALTER TABLE Subscription ADD COLUMN overrideVolumeSetting INTEGER NOT NULL DEFAULT (7)") + } + } } } @@ -336,7 +352,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -349,7 +365,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -362,7 +378,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -375,7 +391,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -388,7 +404,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 52a26b351..1438c9823 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -446,6 +446,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas minPriority = s.minPriority, autoDelete = s.autoDelete, insistent = s.insistent, + overrideVolumeMaxPriority = s.overrideVolumeMaxPriority, + overrideVolumeSetting = s.overrideVolumeSetting, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -473,6 +475,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas minPriority = s.minPriority, autoDelete = s.autoDelete, insistent = s.insistent, + overrideVolumeMaxPriority = s.overrideVolumeMaxPriority, + overrideVolumeSetting = s.overrideVolumeSetting, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -566,6 +570,11 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml const val INSISTENT_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code) + const val OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml + const val OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code) + + const val OVERRIDE_VOLUME_SETTING_DEFAULT = 7 + const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp" const val CONNECTION_PROTOCOL_WS = "ws" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index 08f970b1e..b7d92da45 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -9,6 +9,7 @@ import android.media.AudioAttributes import android.media.AudioManager import android.media.RingtoneManager import android.net.Uri +import android.net.rtp.AudioStream import android.os.Build import android.os.Bundle import android.widget.Toast @@ -91,9 +92,15 @@ class NotificationService(val context: Context) { val title = formatTitle(subscription, notification) val groupId = if (subscription.dedicatedChannels) subscriptionGroupId(subscription) else DEFAULT_GROUP val channelId = toChannelId(groupId, notification.priority) - val overrrideVolume = notification.priority == PRIORITY_MAX && repository.getOverrideVolumeMaxPriorityEnabled() + val overrideVolume = notification.priority == PRIORITY_MAX && + ((repository.getOverrideVolumeMaxPriorityEnabled() && subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL) || subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED) + val volumeSetting = if (subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED) { + subscription.overrideVolumeSetting + } else { + repository.getOverrideVolumeSetting() + } val insistent = notification.priority == PRIORITY_MAX && - (repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED) + ((repository.getInsistentMaxPriorityEnabled() && subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL) || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED) val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, Colors.notificationIcon(context))) @@ -113,7 +120,7 @@ class NotificationService(val context: Context) { maybeCreateNotificationGroup(groupId, subscriptionGroupName(subscription)) maybeCreateNotificationChannel(groupId, notification.priority) - maybeOverrideVolumeAndPlayInsistentSound(groupId, overrrideVolume, insistent) + maybeOverrideVolumeAndPlayInsistentSound(groupId, overrideVolume, volumeSetting, insistent) notificationManager.notify(notification.notificationId, builder.build()) } @@ -353,8 +360,15 @@ class NotificationService(val context: Context) { class DeleteBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log.d(TAG, "Media player: Stopping insistent ring") - val mediaPlayer = Repository.getInstance(context).mediaPlayer + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + var audioStream = AudioManager.STREAM_NOTIFICATION + if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0){ + audioStream = AudioManager.STREAM_ALARM + } + val repository = Repository.getInstance(context) + val mediaPlayer = repository.mediaPlayer mediaPlayer.stop() + audioManager.setStreamVolume(audioStream, repository.getPreviousVolume(), 0) } } @@ -441,28 +455,38 @@ class NotificationService(val context: Context) { } } - private fun maybeOverrideVolumeAndPlayInsistentSound(groupId: String, overrideVolume: Boolean, insistent: Boolean) { - if (!overrideVolume) { + private fun maybeOverrideVolumeAndPlayInsistentSound(groupId: String, overrideVolume: Boolean, volumeSetting: Int, insistent: Boolean) { + if (!overrideVolume && !insistent) { return } try { - val mediaPlayer = repository.mediaPlayer val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val previousVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM) - audioManager.setStreamVolume(AudioManager.STREAM_ALARM, repository.getOverrideVolumeSetting(), 0) - repository.setPreviousVolume(previousVolume) - if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { - Log.d(TAG, "Media player: Playing insistent alarm on alarm channel") - mediaPlayer.reset() - mediaPlayer.setDataSource(context, getInsistentSound(groupId)) - mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) - mediaPlayer.isLooping = insistent - mediaPlayer.setOnCompletionListener { audioManager.setStreamVolume(AudioManager.STREAM_ALARM, repository.getPreviousVolume(), 0) } - mediaPlayer.prepare() - mediaPlayer.start() + var audioStream = AudioManager.STREAM_NOTIFICATION + var usageStream = AudioAttributes.USAGE_NOTIFICATION + if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0){ + audioStream = AudioManager.STREAM_ALARM + usageStream = AudioAttributes.USAGE_ALARM + } + + val mediaPlayer = repository.mediaPlayer + if (overrideVolume) { + val previousVolume = audioManager.getStreamVolume(audioStream) + audioManager.setStreamVolume(audioStream, volumeSetting, 0) + repository.setPreviousVolume(previousVolume) + } + + mediaPlayer.reset() + mediaPlayer.setDataSource(context, getInsistentSound(groupId)) + mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(usageStream).build()) + if (overrideVolume) { + Log.d(TAG, "Media player: Playing alarm on alarm channel") + mediaPlayer.setOnCompletionListener { audioManager.setStreamVolume(audioStream, repository.getPreviousVolume(), 0) } } else { - Log.d(TAG, "Media player: Alarm volume is 0; not playing insistent alarm") + Log.d(TAG, "Media player: Playing insistent alarm on notification channel") } + mediaPlayer.isLooping = insistent + mediaPlayer.prepare() + mediaPlayer.start() } catch (e: Exception) { Log.w(TAG, "Media player: Failed to play insistent alarm", e) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 851f43ac2..dabe8e527 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -116,6 +116,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeMaxPriority = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeSetting = Repository.OVERRIDE_VOLUME_SETTING_DEFAULT, lastNotificationId = null, icon = null, upAppId = null, diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt index ccc0191d2..6771c5235 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -5,6 +5,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.media.AudioManager import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -119,6 +120,8 @@ class DetailSettingsActivity : AppCompatActivity() { loadMinPriorityPref() loadAutoDeletePref() loadInsistentMaxPriorityPref() + loadOverrideVolumeMaxPriorityPref() + loadOverrideVolumeSettingPref() loadIconSetPref() loadIconRemovePref() if (notificationService.channelsSupported()) { @@ -333,6 +336,55 @@ class DetailSettingsActivity : AppCompatActivity() { } } + private fun loadOverrideVolumeMaxPriorityPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_override_volume_max_priority_key) ?: return + val pref: ListPreference = findPreference(prefId) ?: return + pref.isVisible = true + pref.value = subscription.overrideVolumeMaxPriority.toString() + pref.preferenceDataStore = object : PreferenceDataStore() { + override fun putString(key: String?, value: String?) { + val intValue = value?.toIntOrNull() ?:return + val volumeSettingId = context?.getString(R.string.detail_settings_notifications_override_volume_setting_key) + val volumeSetting: SeekBarPreference? = volumeSettingId?.let { findPreference(it) } + volumeSetting?.isVisible = intValue == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED + save(subscription.copy(overrideVolumeMaxPriority = intValue)) + } + override fun getString(key: String?, defValue: String?): String { + return subscription.overrideVolumeMaxPriority.toString() + } + } + pref.summaryProvider = Preference.SummaryProvider { preference -> + val value = preference.value.toIntOrNull() ?: Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL + val global = value == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL + val enabled = if (global) repository.getInsistentMaxPriorityEnabled() else value == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED + val summary = if (enabled) { + getString(R.string.settings_notifications_override_volume_max_priority_summary_enabled) + } else { + getString(R.string.settings_notifications_override_volume_max_priority_summary_disabled) + } + maybeAppendGlobal(summary, global) + } + } + + private fun loadOverrideVolumeSettingPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_override_volume_setting_key) ?: return + val pref: SeekBarPreference = findPreference(prefId) ?: return + pref.isVisible = subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED + pref.value = subscription.overrideVolumeSetting + val audioManager = context?.getSystemService(Context.AUDIO_SERVICE) as AudioManager + pref.max = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM) + pref.preferenceDataStore = object : PreferenceDataStore() { + override fun putInt(key: String?, value: Int) { + save(subscription.copy(overrideVolumeSetting = value)) + } + override fun getInt(key: String?, defValue: Int): Int { + return subscription.overrideVolumeSetting + } + } + } + + + private fun loadIconSetPref() { val prefId = context?.getString(R.string.detail_settings_appearance_icon_set_key) ?: return iconSetPref = findPreference(prefId) ?: return diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index e131f32c7..e1e296aab 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -455,6 +455,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeMaxPriority = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeSetting = Repository.OVERRIDE_VOLUME_SETTING_DEFAULT, lastNotificationId = null, icon = null, upAppId = null, diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index b9db92ab8..2e669f66a 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -254,6 +254,16 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere getString(R.string.settings_notifications_override_volume_setting_summary) } + val overrideActive = repository.getOverrideVolumeMaxPriorityEnabled() + + if (!overrideActive) { + val notificationCategoryPrefId = context?.getString(R.string.settings_notifications_category_key) ?: return + val notificationCategory: PreferenceCategory? = findPreference(notificationCategoryPrefId) + if (overrideVolumeSetting != null) { + notificationCategory?.removePreference(overrideVolumeSetting) + } + } + // Keep alerting for max priority val insistentMaxPriorityPrefId = context?.getString(R.string.settings_notifications_insistent_max_priority_key) ?: return val insistentMaxPriority: SwitchPreference? = findPreference(insistentMaxPriorityPrefId) @@ -274,19 +284,6 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } - val overrideActive = repository.getOverrideVolumeMaxPriorityEnabled() - - if (!overrideActive) { - val notificationCategoryPrefId = context?.getString(R.string.settings_notifications_category_key) ?: return - val notificationCategory: PreferenceCategory? = findPreference(notificationCategoryPrefId) - if (overrideVolumeSetting != null) { - notificationCategory?.removePreference(overrideVolumeSetting) - } - if (insistentMaxPriority != null) { - notificationCategory?.removePreference(insistentMaxPriority) - } - } - // Channel settings val channelPrefsPrefId = context?.getString(R.string.settings_notifications_channel_prefs_key) ?: return val channelPrefs: Preference? = findPreference(channelPrefsPrefId) diff --git a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt index 46a0bcb10..66460f172 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -80,6 +80,8 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeMaxPriority = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL, + overrideVolumeSetting = Repository.OVERRIDE_VOLUME_SETTING_DEFAULT, lastNotificationId = null, icon = null, upAppId = appId, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b00a3f795..52b869468 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -368,6 +368,8 @@ Do Not Disturb (DND) override, sounds, etc. Keep alerting Alert only once + Override System Volume + Use System Volume Appearance Subscription icon Set an icon to be displayed in notifications diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index bdcf58d37..05ecc01ca 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -44,6 +44,8 @@ SubscriptionMinPriority SubscriptionAutoDelete SubscriptionInsistentMaxPriority + SubscriptionOverrideVolumeMaxPriority + SubscriptionOverrideVolumeSetting SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -164,6 +166,16 @@ 1 0 + + @string/detail_settings_global_setting_title + @string/detail_settings_notifications_override_volume_max_priority_list_item_enabled + @string/detail_settings_notifications_override_volume_max_priority_list_item_disabled + + + -1 + 1 + 0 + @string/settings_advanced_connection_protocol_entry_jsonhttp @string/settings_advanced_connection_protocol_entry_ws diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index 38cd18b79..89203218c 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -29,12 +29,28 @@ app:defaultValue="-1" app:isPreferenceVisible="false"/> + app:isPreferenceVisible="false"/> + +