From 55ad2e65b5a7ea521bf8bcc211f563ae9f9d4e8c Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Tue, 29 Nov 2022 22:46:38 -0500 Subject: [PATCH] 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"/> +