From b3a0a10b605a7b49b2d7f4c51d1cec54050899fe Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Mon, 28 Nov 2022 06:00:41 -0500 Subject: [PATCH 1/6] Constant ring --- app/src/main/AndroidManifest.xml | 2 + .../io/heckel/ntfy/msg/NotificationService.kt | 59 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4cbdf604..35689fd2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -134,6 +134,8 @@ android:exported="false"> + + Date: Tue, 29 Nov 2022 22:46:38 -0500 Subject: [PATCH 2/6] Works! --- app/src/main/AndroidManifest.xml | 7 +- .../java/io/heckel/ntfy/app/Application.kt | 6 - .../main/java/io/heckel/ntfy/db/Repository.kt | 15 +++ .../io/heckel/ntfy/msg/NotificationService.kt | 103 ++++++++---------- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 1 + .../io/heckel/ntfy/ui/SettingsActivity.kt | 20 ++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/values.xml | 1 + app/src/main/res/xml/main_preferences.xml | 4 + 9 files changed, 98 insertions(+), 62 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 35689fd2..8120b56a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -134,7 +134,12 @@ android:exported="false"> - + + + () private val connectionStatesLiveData = MutableLiveData(connectionStates) + + // TODO Move these into an ApplicationState singleton val detailViewSubscriptionId = AtomicLong(0L) // Omg, what a hack ... + val mediaPlayer = MediaPlayer() init { Log.d(TAG, "Created $this") @@ -288,6 +292,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas .apply() } + fun getInsistentMaxPriorityEnabled(): Boolean { + return sharedPrefs.getBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, false) // Disabled by default + } + + fun setInsistentMaxPriorityEnabled(enabled: Boolean) { + sharedPrefs.edit() + .putBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, enabled) + .apply() + } + fun getRecordLogs(): Boolean { return sharedPrefs.getBoolean(SHARED_PREFS_RECORD_LOGS_ENABLED, false) // Disabled by default } @@ -459,6 +473,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol" const val SHARED_PREFS_DARK_MODE = "DarkMode" const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled" + const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority" 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 a2adb41e..6f46ca60 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -7,7 +7,6 @@ import android.content.Context import android.content.Intent import android.media.AudioAttributes import android.media.AudioManager -import android.media.MediaPlayer import android.media.RingtoneManager import android.net.Uri import android.os.Build @@ -24,9 +23,9 @@ import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.util.* import java.util.* - class NotificationService(val context: Context) { private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val repository = Repository.getInstance(context) fun display(subscription: Subscription, notification: Notification) { Log.d(TAG, "Displaying notification $notification") @@ -66,6 +65,7 @@ class NotificationService(val context: Context) { private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) { val title = formatTitle(subscription, notification) val channelId = toChannelId(notification.priority) + val insistent = notification.priority == 5 && repository.getInsistentMaxPriorityEnabled() val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, Colors.notificationIcon(context))) @@ -74,7 +74,8 @@ class NotificationService(val context: Context) { .setAutoCancel(true) // Cancel when notification is clicked setStyleAndText(builder, subscription, notification) // Preview picture or big text style setClickAction(builder, subscription, notification) - maybeSetSound(builder, update) + maybeSetDeleteIntent(builder, insistent) + maybeSetSound(builder, insistent, update) maybeSetProgress(builder, notification) maybeAddOpenAction(builder, notification) maybeAddBrowseAction(builder, notification) @@ -82,65 +83,24 @@ class NotificationService(val context: Context) { maybeAddCancelAction(builder, notification) maybeAddUserActions(builder, notification) - - maybeCreateNotificationChannel(notification.priority) - val systemNotification = builder.build() - if (channelId == CHANNEL_ID_MAX) { - //systemNotification.flags = systemNotification.flags or android.app.Notification.FLAG_INSISTENT - } - notificationManager.notify(notification.notificationId, systemNotification) + maybePlayInsistentSound(insistent) - if (channelId == CHANNEL_ID_MAX) { - Log.d(TAG, "Setting alarm") - /*val calendar = Calendar.getInstance() - val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager - val intent = Intent(context, AlarmReceiver::class.java) - val pendingIntent = PendingIntent.getBroadcast(context, 1111, intent, PendingIntent.FLAG_IMMUTABLE) - // when using setAlarmClock() it displays a notification until alarm rings and when pressed it takes us to mainActivity - - alarmManager?.set( - AlarmManager.RTC_WAKEUP, - calendar.timeInMillis, pendingIntent - )*/ - - val alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - val mMediaPlayer = MediaPlayer() - - mMediaPlayer.setDataSource(context, alert) - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { - mMediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) - mMediaPlayer.isLooping = true; - mMediaPlayer.prepare(); - mMediaPlayer.start(); - mMediaPlayer.stop() - } - - } + notificationManager.notify(notification.notificationId, builder.build()) } - class AlarmReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - Log.d(TAG, "AlarmReceiver.onReceive ${intent}") - val context = context ?: return - - val alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) - val mMediaPlayer = MediaPlayer() - - mMediaPlayer.setDataSource(context, alert) - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - mMediaPlayer.setLooping(true); - mMediaPlayer.prepare(); - mMediaPlayer.start(); - } + private fun maybeSetDeleteIntent(builder: NotificationCompat.Builder, insistent: Boolean) { + if (!insistent) { + return } + val intent = Intent(context, DeleteBroadcastReceiver::class.java) + val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE) + builder.setDeleteIntent(pendingIntent) } - private fun maybeSetSound(builder: NotificationCompat.Builder, update: Boolean) { - if (!update) { + private fun maybeSetSound(builder: NotificationCompat.Builder, insistent: Boolean, update: Boolean) { + val hasSound = !update && !insistent + if (hasSound) { val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) builder.setSound(defaultSoundUri) } else { @@ -353,6 +313,17 @@ class NotificationService(val context: Context) { } } + /** + * Receives a broadcast when a notification is swiped away. This is currently + * only called for notifications with an insistent sound. + */ + class DeleteBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val mediaPlayer = Repository.getInstance(context).mediaPlayer + mediaPlayer.stop() + } + } + private fun detailActivityIntent(subscription: Subscription): PendingIntent? { val intent = Intent(context, DetailActivity::class.java).apply { putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id) @@ -416,6 +387,28 @@ class NotificationService(val context: Context) { } } + private fun maybePlayInsistentSound(insistent: Boolean) { + if (!insistent) { + return + } + try { + Log.d(TAG, "Playing insistent alarm") + val mediaPlayer = repository.mediaPlayer + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { + mediaPlayer.reset() + mediaPlayer.setDataSource(context, alert) + mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) + mediaPlayer.isLooping = true; + mediaPlayer.prepare() + mediaPlayer.start() + } + } catch (e: Exception) { + Log.w(TAG, "Failed playing insistent alarm", e) + } + } + /** * Activity used to launch a URL. * . 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 4fc9ec77..0eec67b3 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -297,6 +297,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra } } } + repository.mediaPlayer.stop() } override fun onCreateOptionsMenu(menu: Menu): Boolean { 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 cc6f8824..859232ab 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -200,6 +200,26 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } } + // Keep alerting for max priority + val insistentMaxPriorityPrefId = context?.getString(R.string.settings_notifications_insistent_max_priority_key) ?: return + val insistentMaxPriority: SwitchPreference? = findPreference(insistentMaxPriorityPrefId) + insistentMaxPriority?.isChecked = repository.getInsistentMaxPriorityEnabled() + insistentMaxPriority?.preferenceDataStore = object : PreferenceDataStore() { + override fun putBoolean(key: String?, value: Boolean) { + repository.setInsistentMaxPriorityEnabled(value) + } + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + return repository.getInsistentMaxPriorityEnabled() + } + } + insistentMaxPriority?.summaryProvider = Preference.SummaryProvider { pref -> + if (pref.isChecked) { + getString(R.string.settings_notifications_insistent_max_priority_summary_enabled) + } else { + getString(R.string.settings_notifications_insistent_max_priority_summary_disabled) + } + } + // 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 59c9817d..b8ea38bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,6 +279,9 @@ After one week After one month After 3 months + Keep alerting for highest priority + Max priority notifications continuously play the notification sound until dismissed. This overrides Do Not Disturb mode. + Max priority notifications alert only once. If enabled, the notification sound will repeat and override Do Not Disturb mode. General Default server Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics. diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 2ccbda87..0b33496b 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -18,6 +18,7 @@ ChannelPrefs AutoDownload AutoDelete + InsistentMaxPriority DefaultBaseURL ManageUsers DarkMode diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index 42603a2a..d7178a64 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -25,6 +25,10 @@ app:entries="@array/settings_notifications_auto_delete_entries" app:entryValues="@array/settings_notifications_auto_delete_values" app:defaultValue="2592000"/> + Date: Sun, 4 Dec 2022 20:11:46 -0500 Subject: [PATCH 3/6] WIP constant ring --- .../main/java/io/heckel/ntfy/db/Database.kt | 1 + .../io/heckel/ntfy/msg/NotificationService.kt | 7 +++-- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 8 +++++- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 26 ++++++++++++++++++- app/src/main/res/values/strings.xml | 4 +-- 5 files changed, 40 insertions(+), 6 deletions(-) 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 62daf0a0..effdba00 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -18,6 +18,7 @@ data class Subscription( @ColumnInfo(name = "mutedUntil") val mutedUntil: Long, @ColumnInfo(name = "minPriority") val minPriority: Int, @ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds + //@ColumnInfo(name = "insistent") val insistent: Boolean?, // Seconds @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 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 6f46ca60..f4911685 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -319,6 +319,7 @@ 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 mediaPlayer.stop() } @@ -392,20 +393,22 @@ class NotificationService(val context: Context) { return } try { - Log.d(TAG, "Playing insistent alarm") val mediaPlayer = repository.mediaPlayer val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager val alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { + Log.d(TAG, "Media player: Playing insistent alarm on alarm channel") mediaPlayer.reset() mediaPlayer.setDataSource(context, alert) mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) mediaPlayer.isLooping = true; mediaPlayer.prepare() mediaPlayer.start() + } else { + Log.d(TAG, "Media player: Alarm volume is 0; not playing insistent alarm") } } catch (e: Exception) { - Log.w(TAG, "Failed playing insistent alarm", e) + 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 0eec67b3..e900d704 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -254,6 +254,13 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra // Mark this subscription as "open" so we don't receive notifications for it repository.detailViewSubscriptionId.set(subscriptionId) + + // Stop insistent playback (if running, otherwise it'll throw) + try { + repository.mediaPlayer.stop() + } catch (_: Exception) { + // Ignore errors + } } override fun onResume() { @@ -297,7 +304,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra } } } - repository.mediaPlayer.stop() } override fun onCreateOptionsMenu(menu: Menu): Boolean { 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 91b41d17..e5108a3e 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -110,6 +110,7 @@ class DetailSettingsActivity : AppCompatActivity() { loadMutedUntilPref() loadMinPriorityPref() loadAutoDeletePref() + //loadInsistentMaxPriority() loadIconSetPref() loadIconRemovePref() } else { @@ -252,7 +253,30 @@ class DetailSettingsActivity : AppCompatActivity() { maybeAppendGlobal(summary, global) } } - +/* + private fun loadInsistentMaxPriority() { + val appBaseUrl = getString(R.string.app_base_url) + val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return + val pref: SwitchPreference? = findPreference(prefId) + pref?.isVisible = true + pref?.isChecked = subscription.instant + pref?.preferenceDataStore = object : PreferenceDataStore() { + override fun putBoolean(key: String?, value: Boolean) { + save(subscription.copy(instant = value), refresh = true) + } + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + return subscription.instant + } + } + pref?.summaryProvider = Preference.SummaryProvider { preference -> + if (preference.isChecked) { + getString(R.string.detail_settings_notifications_instant_summary_on) + } else { + getString(R.string.detail_settings_notifications_instant_summary_off) + } + } + } +*/ 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/res/values/strings.xml b/app/src/main/res/values/strings.xml index b8ea38bd..77089bc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -280,8 +280,8 @@ After one month After 3 months Keep alerting for highest priority - Max priority notifications continuously play the notification sound until dismissed. This overrides Do Not Disturb mode. - Max priority notifications alert only once. If enabled, the notification sound will repeat and override Do Not Disturb mode. + Max priority notifications continuously alert until dismissed + If enabled, the notification sound will continuously repeat until dismissed General Default server Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics. From 96d867c1a63a2e662de2385d6af0b7a0bbecc98b Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Sun, 4 Dec 2022 20:28:57 -0500 Subject: [PATCH 4/6] Comment --- app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt | 1 + 1 file changed, 1 insertion(+) 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 f4911685..53e7fe9b 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -99,6 +99,7 @@ class NotificationService(val context: Context) { } private fun maybeSetSound(builder: NotificationCompat.Builder, insistent: Boolean, update: Boolean) { + // Note that the sound setting is ignored in Android => O (26) in favor of notification channels val hasSound = !update && !insistent if (hasSound) { val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) From 5101d1a0b37bf40e466e7209a531dd6d75a0af61 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Tue, 6 Dec 2022 15:29:09 -0500 Subject: [PATCH 5/6] Make subscription-specific setting work --- .../io.heckel.ntfy.db.Database/12.json | 12 +++-- .../java/io/heckel/ntfy/backup/Backuper.kt | 3 ++ .../main/java/io/heckel/ntfy/db/Database.kt | 49 ++++++++++++++++--- .../main/java/io/heckel/ntfy/db/Repository.kt | 6 +++ .../io/heckel/ntfy/msg/NotificationService.kt | 3 +- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 5 +- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 38 +++++++------- .../java/io/heckel/ntfy/ui/MainActivity.kt | 1 + .../io/heckel/ntfy/up/BroadcastReceiver.kt | 1 + app/src/main/res/values/strings.xml | 4 +- app/src/main/res/values/values.xml | 11 +++++ app/src/main/res/xml/detail_preferences.xml | 7 +++ 12 files changed, 108 insertions(+), 32 deletions(-) diff --git a/app/schemas/io.heckel.ntfy.db.Database/12.json b/app/schemas/io.heckel.ntfy.db.Database/12.json index e9e36a13..801d9ed9 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/12.json +++ b/app/schemas/io.heckel.ntfy.db.Database/12.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 12, - "identityHash": "d230005f4d9824ba9aa34c61003bdcbb", + "identityHash": "40948c056fa4ccc9765735111177cf85", "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, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, 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, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -50,6 +50,12 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "insistent", + "columnName": "insistent", + "affinity": "INTEGER", + "notNull": true + }, { "fieldPath": "lastNotificationId", "columnName": "lastNotificationId", @@ -338,7 +344,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, 'd230005f4d9824ba9aa34c61003bdcbb')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '40948c056fa4ccc9765735111177cf85')" ] } } \ 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 8c87f90f..f4e7b4e0 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -102,6 +102,7 @@ class Backuper(val context: Context) { mutedUntil = s.mutedUntil, 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, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -239,6 +240,7 @@ class Backuper(val context: Context) { mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -356,6 +358,7 @@ data class Subscription( val mutedUntil: Long, val minPriority: Int?, val autoDelete: Long?, + val insistent: 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 effdba00..141cf13e 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -18,7 +18,7 @@ data class Subscription( @ColumnInfo(name = "mutedUntil") val mutedUntil: Long, @ColumnInfo(name = "minPriority") val minPriority: Int, @ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds - //@ColumnInfo(name = "insistent") val insistent: Boolean?, // Seconds + @ColumnInfo(name = "insistent") val insistent: Int, // Ring constantly for max priority notifications (-1 = use global, 0 = off, 1 = on) @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 @@ -29,8 +29,40 @@ data class Subscription( @Ignore val lastActive: Long = 0, // Unix timestamp @Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE ) { - constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, lastNotificationId: String, icon: String, upAppId: String, upConnectorToken: String, displayName: String?) : - this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, lastNotificationId, icon, upAppId, upConnectorToken, displayName, 0, 0, 0, ConnectionState.NOT_APPLICABLE) + constructor( + id: Long, + baseUrl: String, + topic: String, + instant: Boolean, + mutedUntil: Long, + minPriority: Int, + autoDelete: Long, + insistent: Int, + lastNotificationId: String, + icon: String, + upAppId: String, + upConnectorToken: String, + displayName: String? + ) : + this( + id, + baseUrl, + topic, + instant, + mutedUntil, + minPriority, + autoDelete, + insistent, + lastNotificationId, + icon, + upAppId, + upConnectorToken, + displayName, + 0, + 0, + 0, + ConnectionState.NOT_APPLICABLE + ) } enum class ConnectionState { @@ -45,6 +77,7 @@ data class SubscriptionWithMetadata( val mutedUntil: Long, val autoDelete: Long, val minPriority: Int, + val insistent: Int, val lastNotificationId: String?, val icon: String?, val upAppId: String?, @@ -291,7 +324,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + 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, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -304,7 +337,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + 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, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -317,7 +350,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + 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, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -330,7 +363,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + 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, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -343,7 +376,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + 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, 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 d92e4a82..e5344f06 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -402,6 +402,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -427,6 +428,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -505,6 +507,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val AUTO_DELETE_THREE_MONTHS_SECONDS = 90 * ONE_DAY_SECONDS const val AUTO_DELETE_DEFAULT_SECONDS = AUTO_DELETE_ONE_MONTH_SECONDS + const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml + const val INSISTENT_MAX_PRIORITY_DISABLED = 0 + const val INSISTENT_MAX_PRIORITY_ENABLED = 1 + 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 2a1ee127..42e225b8 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -65,7 +65,8 @@ class NotificationService(val context: Context) { private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) { val title = formatTitle(subscription, notification) val channelId = toChannelId(notification.priority) - val insistent = notification.priority == 5 && repository.getInsistentMaxPriorityEnabled() + val insistent = notification.priority == 5 && + (repository.getInsistentMaxPriorityEnabled() || 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))) 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 e900d704..e29e9192 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -113,6 +113,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = null, icon = null, upAppId = null, @@ -241,7 +242,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { if (positionStart == 0) { - Log.d(TAG, "$itemCount item(s) inserted at $positionStart, scrolling to the top") + Log.d(TAG, "$itemCount item(s) inserted at 0, scrolling to the top") mainList.scrollToPosition(positionStart) } } @@ -626,7 +627,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra handleActionModeClick(notification) } else if (notification.click != "") { try { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click))) + startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click))) } catch (e: Exception) { Log.w(TAG, "Cannot open click URL", e) runOnUiThread { 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 e5108a3e..1be59e2c 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -110,7 +110,7 @@ class DetailSettingsActivity : AppCompatActivity() { loadMutedUntilPref() loadMinPriorityPref() loadAutoDeletePref() - //loadInsistentMaxPriority() + loadInsistentMaxPriorityPref() loadIconSetPref() loadIconRemovePref() } else { @@ -238,7 +238,7 @@ class DetailSettingsActivity : AppCompatActivity() { pref?.summaryProvider = Preference.SummaryProvider { preference -> var seconds = preference.value.toLongOrNull() ?: Repository.AUTO_DELETE_USE_GLOBAL val global = seconds == Repository.AUTO_DELETE_USE_GLOBAL - if (seconds == Repository.AUTO_DELETE_USE_GLOBAL) { + if (global) { seconds = repository.getAutoDeleteSeconds() } val summary = when (seconds) { @@ -253,30 +253,34 @@ class DetailSettingsActivity : AppCompatActivity() { maybeAppendGlobal(summary, global) } } -/* - private fun loadInsistentMaxPriority() { - val appBaseUrl = getString(R.string.app_base_url) - val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return - val pref: SwitchPreference? = findPreference(prefId) + + private fun loadInsistentMaxPriorityPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_insistent_max_priority_key) ?: return + val pref: ListPreference? = findPreference(prefId) pref?.isVisible = true - pref?.isChecked = subscription.instant + pref?.value = subscription.insistent.toString() pref?.preferenceDataStore = object : PreferenceDataStore() { - override fun putBoolean(key: String?, value: Boolean) { - save(subscription.copy(instant = value), refresh = true) + override fun putString(key: String?, value: String?) { + val intValue = value?.toIntOrNull() ?:return + save(subscription.copy(insistent = intValue)) } - override fun getBoolean(key: String?, defValue: Boolean): Boolean { - return subscription.instant + override fun getString(key: String?, defValue: String?): String { + return subscription.insistent.toString() } } - pref?.summaryProvider = Preference.SummaryProvider { preference -> - if (preference.isChecked) { - getString(R.string.detail_settings_notifications_instant_summary_on) + pref?.summaryProvider = Preference.SummaryProvider { preference -> + val value = preference.value.toIntOrNull() ?: Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL + val global = value == Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL + val enabled = if (global) repository.getInsistentMaxPriorityEnabled() else value == Repository.INSISTENT_MAX_PRIORITY_ENABLED + val summary = if (enabled) { + getString(R.string.settings_notifications_insistent_max_priority_summary_enabled) } else { - getString(R.string.detail_settings_notifications_instant_summary_off) + getString(R.string.settings_notifications_insistent_max_priority_summary_disabled) } + maybeAppendGlobal(summary, global) } } -*/ + 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 ad928a4e..6b027911 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,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = null, icon = null, upAppId = null, 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 9119e15d..9110cb96 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -75,6 +75,7 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, 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 82f1d79d..33be8df2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -282,7 +282,7 @@ After 3 months Keep alerting for highest priority Max priority notifications continuously alert until dismissed - If enabled, the notification sound will continuously repeat until dismissed + Max priority notifications only alert once General Default server Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics. @@ -352,6 +352,8 @@ Instant delivery Notifications are delivered instantly. Requires a foreground service and consumes more battery. Notifications are delivered using Firebase. Delivery may be delayed, but consumes less battery. + Keep alerting + Alert only once 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 0b33496b..0dc8b2b4 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -37,6 +37,7 @@ SubscriptionMutedUntil SubscriptionMinPriority SubscriptionAutoDelete + SubscriptionInsistentMaxPriority SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -147,6 +148,16 @@ 2592000 7776000 + + @string/detail_settings_global_setting_title + @string/detail_settings_notifications_insistent_max_priority_list_item_enabled + @string/detail_settings_notifications_insistent_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 e209f16b..0f56d86a 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -28,6 +28,13 @@ app:entryValues="@array/detail_settings_notifications_auto_delete_values" app:defaultValue="-1" app:isPreferenceVisible="false"/> + Date: Tue, 6 Dec 2022 15:48:04 -0500 Subject: [PATCH 6/6] Use custom sound from channel for insistent sound --- .../java/io/heckel/ntfy/msg/ApiService.kt | 4 +-- .../io/heckel/ntfy/msg/NotificationService.kt | 32 ++++++++++++------- .../java/io/heckel/ntfy/ui/DetailAdapter.kt | 10 +++--- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 4 +-- .../io/heckel/ntfy/ui/SettingsActivity.kt | 4 +-- .../java/io/heckel/ntfy/util/Constants.kt | 11 +++++++ app/src/main/java/io/heckel/ntfy/util/Util.kt | 19 +++++------ 7 files changed, 50 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/io/heckel/ntfy/util/Constants.kt diff --git a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt index 64baa451..852b6827 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt @@ -37,7 +37,7 @@ class ApiService { user: User? = null, message: String, title: String = "", - priority: Int = 3, + priority: Int = PRIORITY_DEFAULT, tags: List = emptyList(), delay: String = "", body: RequestBody? = null, @@ -45,7 +45,7 @@ class ApiService { ) { val url = topicUrl(baseUrl, topic) val query = mutableListOf() - if (priority in 1..5) { + if (priority in ALL_PRIORITIES) { query.add("priority=$priority") } if (tags.isNotEmpty()) { 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 42e225b8..22ec9c88 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -59,13 +59,13 @@ class NotificationService(val context: Context) { } fun createNotificationChannels() { - (1..5).forEach { priority -> maybeCreateNotificationChannel(priority) } + ALL_PRIORITIES.forEach { priority -> maybeCreateNotificationChannel(priority) } } private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) { val title = formatTitle(subscription, notification) val channelId = toChannelId(notification.priority) - val insistent = notification.priority == 5 && + val insistent = notification.priority == PRIORITY_MAX && (repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED) val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) @@ -351,9 +351,9 @@ class NotificationService(val context: Context) { val pause = 300L val channel = when (priority) { - 1 -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN) - 2 -> NotificationChannel(CHANNEL_ID_LOW, context.getString(R.string.channel_notifications_low_name), NotificationManager.IMPORTANCE_LOW) - 4 -> { + PRIORITY_MIN -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN) + PRIORITY_LOW -> NotificationChannel(CHANNEL_ID_LOW, context.getString(R.string.channel_notifications_low_name), NotificationManager.IMPORTANCE_LOW) + PRIORITY_HIGH -> { val channel = NotificationChannel(CHANNEL_ID_HIGH, context.getString(R.string.channel_notifications_high_name), NotificationManager.IMPORTANCE_HIGH) channel.enableVibration(true) channel.vibrationPattern = longArrayOf( @@ -362,7 +362,7 @@ class NotificationService(val context: Context) { ) channel } - 5 -> { + PRIORITY_MAX -> { val channel = NotificationChannel(CHANNEL_ID_MAX, context.getString(R.string.channel_notifications_max_name), NotificationManager.IMPORTANCE_HIGH) // IMPORTANCE_MAX does not exist channel.enableLights(true) channel.enableVibration(true) @@ -385,10 +385,10 @@ class NotificationService(val context: Context) { private fun toChannelId(priority: Int): String { return when (priority) { - 1 -> CHANNEL_ID_MIN - 2 -> CHANNEL_ID_LOW - 4 -> CHANNEL_ID_HIGH - 5 -> CHANNEL_ID_MAX + PRIORITY_MIN -> CHANNEL_ID_MIN + PRIORITY_LOW -> CHANNEL_ID_LOW + PRIORITY_HIGH -> CHANNEL_ID_HIGH + PRIORITY_MAX -> CHANNEL_ID_MAX else -> CHANNEL_ID_DEFAULT } } @@ -400,11 +400,10 @@ class NotificationService(val context: Context) { try { val mediaPlayer = repository.mediaPlayer val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { Log.d(TAG, "Media player: Playing insistent alarm on alarm channel") mediaPlayer.reset() - mediaPlayer.setDataSource(context, alert) + mediaPlayer.setDataSource(context, getInsistentSound()) mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()) mediaPlayer.isLooping = true; mediaPlayer.prepare() @@ -417,6 +416,15 @@ class NotificationService(val context: Context) { } } + private fun getInsistentSound(): Uri { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = notificationManager.getNotificationChannel(toChannelId(PRIORITY_MAX)) + channel.sound + } else { + RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + } + } + /** * Activity used to launch a URL. * . diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt index 9aca884e..b5444148 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt @@ -143,22 +143,22 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: private fun renderPriority(context: Context, notification: Notification) { when (notification.priority) { - 1 -> { + PRIORITY_MIN -> { priorityImageView.visibility = View.VISIBLE priorityImageView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_priority_1_24dp)) } - 2 -> { + PRIORITY_LOW -> { priorityImageView.visibility = View.VISIBLE priorityImageView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_priority_2_24dp)) } - 3 -> { + PRIORITY_DEFAULT -> { priorityImageView.visibility = View.GONE } - 4 -> { + PRIORITY_HIGH -> { priorityImageView.visibility = View.VISIBLE priorityImageView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_priority_4_24dp)) } - 5 -> { + PRIORITY_MAX -> { priorityImageView.visibility = View.VISIBLE priorityImageView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_priority_5_24dp)) } 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 1be59e2c..3096f396 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -210,8 +210,8 @@ class DetailSettingsActivity : AppCompatActivity() { value = repository.getMinPriority() } val summary = when (value) { - 1 -> getString(R.string.settings_notifications_min_priority_summary_any) - 5 -> getString(R.string.settings_notifications_min_priority_summary_max) + PRIORITY_MIN -> getString(R.string.settings_notifications_min_priority_summary_any) + PRIORITY_MAX -> getString(R.string.settings_notifications_min_priority_summary_max) else -> { val minPriorityString = toPriorityString(requireContext(), value) getString(R.string.settings_notifications_min_priority_summary_x_or_higher, value, minPriorityString) 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 859232ab..a03942ac 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -191,8 +191,8 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere } minPriority?.summaryProvider = Preference.SummaryProvider { pref -> when (val minPriorityValue = pref.value.toIntOrNull() ?: 1) { // 1/low means all priorities - 1 -> getString(R.string.settings_notifications_min_priority_summary_any) - 5 -> getString(R.string.settings_notifications_min_priority_summary_max) + PRIORITY_MIN -> getString(R.string.settings_notifications_min_priority_summary_any) + PRIORITY_MAX -> getString(R.string.settings_notifications_min_priority_summary_max) else -> { val minPriorityString = toPriorityString(requireContext(), minPriorityValue) getString(R.string.settings_notifications_min_priority_summary_x_or_higher, minPriorityValue, minPriorityString) diff --git a/app/src/main/java/io/heckel/ntfy/util/Constants.kt b/app/src/main/java/io/heckel/ntfy/util/Constants.kt new file mode 100644 index 00000000..d708011f --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/util/Constants.kt @@ -0,0 +1,11 @@ +package io.heckel.ntfy.util + +const val ANDROID_APP_MIME_TYPE = "application/vnd.android.package-archive" + +const val PRIORITY_MIN = 1 +const val PRIORITY_LOW = 2 +const val PRIORITY_DEFAULT = 3 +const val PRIORITY_HIGH = 4 +const val PRIORITY_MAX = 5 + +val ALL_PRIORITIES = listOf(PRIORITY_MIN, PRIORITY_LOW, PRIORITY_DEFAULT, PRIORITY_HIGH, PRIORITY_MAX) diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 078bf271..c0643735 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -92,17 +92,16 @@ fun formatDateShort(timestampSecs: Long): String { } fun toPriority(priority: Int?): Int { - if (priority != null && (1..5).contains(priority)) return priority - else return 3 + return if (priority != null && ALL_PRIORITIES.contains(priority)) priority else PRIORITY_DEFAULT } fun toPriorityString(context: Context, priority: Int): String { return when (priority) { - 1 -> context.getString(R.string.settings_notifications_priority_min) - 2 -> context.getString(R.string.settings_notifications_priority_low) - 3 -> context.getString(R.string.settings_notifications_priority_default) - 4 -> context.getString(R.string.settings_notifications_priority_high) - 5 -> context.getString(R.string.settings_notifications_priority_max) + PRIORITY_MIN -> context.getString(R.string.settings_notifications_priority_min) + PRIORITY_LOW -> context.getString(R.string.settings_notifications_priority_low) + PRIORITY_DEFAULT -> context.getString(R.string.settings_notifications_priority_default) + PRIORITY_HIGH -> context.getString(R.string.settings_notifications_priority_high) + PRIORITY_MAX -> context.getString(R.string.settings_notifications_priority_max) else -> context.getString(R.string.settings_notifications_priority_default) } } @@ -322,8 +321,6 @@ fun formatBytes(bytes: Long, decimals: Int = 1): String { return java.lang.String.format("%.${decimals}f %cB", value / 1024.0, ci.current()) } -const val androidAppMimeType = "application/vnd.android.package-archive" - fun mimeTypeToIconResource(mimeType: String?): Int { return if (mimeType?.startsWith("image/") == true) { R.drawable.ic_file_image_red_24dp @@ -331,7 +328,7 @@ fun mimeTypeToIconResource(mimeType: String?): Int { R.drawable.ic_file_video_orange_24dp } else if (mimeType?.startsWith("audio/") == true) { R.drawable.ic_file_audio_purple_24dp - } else if (mimeType == androidAppMimeType) { + } else if (mimeType == ANDROID_APP_MIME_TYPE) { R.drawable.ic_file_app_gray_24dp } else { R.drawable.ic_file_document_blue_24dp @@ -345,7 +342,7 @@ fun supportedImage(mimeType: String?): Boolean { // Google Play doesn't allow us to install received .apk files anymore. // See https://github.com/binwiederhier/ntfy/issues/531 fun canOpenAttachment(attachment: Attachment?): Boolean { - if (attachment?.type == androidAppMimeType && !BuildConfig.INSTALL_PACKAGES_AVAILABLE) { + if (attachment?.type == ANDROID_APP_MIME_TYPE && !BuildConfig.INSTALL_PACKAGES_AVAILABLE) { return false } return true