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 4cbdf60..35689fd 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 35689fd..8120b56 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 a2adb41..6f46ca6 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 4fc9ec7..0eec67b 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 cc6f882..859232a 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 59c9817..b8ea38b 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 2ccbda8..0b33496 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 42603a2..d7178a6 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 62daf0a..effdba0 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 6f46ca6..f491168 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 0eec67b..e900d70 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 91b41d1..e5108a3 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 b8ea38b..77089bc 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 f491168..53e7fe9 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 e9e36a1..801d9ed 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 8c87f90..f4e7b4e 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 effdba0..141cf13 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 d92e4a8..e5344f0 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 2a1ee12..42e225b 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 e900d70..e29e919 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 e5108a3..1be59e2 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 ad928a4..6b02791 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 9119e15..9110cb9 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 82f1d79..33be8df 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 0b33496..0dc8b2b 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 e209f16..0f56d86 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 64baa45..852b682 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 42e225b..22ec9c8 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 9aca884..b544414 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 1be59e2..3096f39 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 859232a..a03942a 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 0000000..d708011 --- /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 078bf27..c064373 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