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 a939fbe06..7356e0b26 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, @@ -255,6 +257,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, @@ -375,6 +379,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 665f210c7..037635657 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?, @@ -201,7 +209,7 @@ data class LogEntry( this(0, timestamp, tag, level, message, exception) } -@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 14) +@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 15) @TypeConverters(Converters::class) abstract class Database : RoomDatabase() { abstract fun subscriptionDao(): SubscriptionDao @@ -230,6 +238,7 @@ abstract class Database : RoomDatabase() { .addMigrations(MIGRATION_11_12) .addMigrations(MIGRATION_12_13) .addMigrations(MIGRATION_13_14) + .addMigrations(MIGRATION_14_15) .fallbackToDestructiveMigration() .build() this.instance = instance @@ -341,6 +350,13 @@ abstract class Database : RoomDatabase() { db.execSQL("ALTER TABLE Notification ADD COLUMN contentType TEXT NOT NULL DEFAULT ('')") } } + + private val MIGRATION_14_15 = object : Migration(14, 15) { + 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)") + } + } } } @@ -348,7 +364,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 @@ -361,7 +377,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 @@ -374,7 +390,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 @@ -387,7 +403,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 @@ -400,7 +416,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 15f76db72..3bfd1376d 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 } @@ -424,6 +456,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, @@ -451,6 +485,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, @@ -485,6 +521,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" @@ -499,6 +543,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) @@ -534,6 +581,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 48067073a..608964894 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.text.SpannedString @@ -94,8 +95,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 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))) @@ -115,7 +123,7 @@ class NotificationService(val context: Context) { maybeCreateNotificationGroup(groupId, subscriptionGroupName(subscription)) maybeCreateNotificationChannel(groupId, notification.priority) - maybePlayInsistentSound(groupId, insistent) + maybeOverrideVolumeAndPlayInsistentSound(groupId, overrideVolume, volumeSetting, insistent) notificationManager.notify(notification.notificationId, builder.build()) } @@ -355,8 +363,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) } } @@ -443,24 +458,38 @@ class NotificationService(val context: Context) { } } - private fun maybePlayInsistentSound(groupId: String, insistent: Boolean) { - if (!insistent) { + 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 - 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.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 39470d7b1..d8b499762 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -518,6 +518,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 2c73ef27d..6a6f564df 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -7,7 +7,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 @@ -49,7 +51,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 @@ -60,9 +62,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) { @@ -85,6 +89,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 @@ -209,6 +224,55 @@ 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) + } + + 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) 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 49f9082b9..3547e5558 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,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 @@ -372,6 +377,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 b6f5f7f5c..4d1530f45 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 @@ -42,6 +45,8 @@ SubscriptionMinPriority SubscriptionAutoDelete SubscriptionInsistentMaxPriority + SubscriptionOverrideVolumeMaxPriority + SubscriptionOverrideVolumeSetting SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -162,6 +167,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"/> + + - + + +