From ddd902e17e298b4b7dade7746f79da5dad845a2a Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Mon, 13 Feb 2023 12:24:40 +0100 Subject: [PATCH 1/9] Implement optional separate app notification channels --- .../com/github/gotify/NotificationSupport.kt | 179 +++++++++++++++--- .../gotify/init/InitializationActivity.kt | 6 +- .../gotify/messages/MessagesActivity.kt | 10 + .../github/gotify/service/WebSocketService.kt | 28 ++- .../gotify/settings/SettingsActivity.kt | 31 ++- app/src/main/res/values/arrays.xml | 1 + app/src/main/res/values/strings.xml | 11 ++ app/src/main/res/xml/root_preferences.xml | 9 + 8 files changed, 238 insertions(+), 37 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt index 46a5287..cfebb3b 100644 --- a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt +++ b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt @@ -1,56 +1,92 @@ package com.github.gotify import android.app.NotificationChannel +import android.app.NotificationChannelGroup import android.app.NotificationManager +import android.content.Context import android.graphics.Color import android.os.Build import androidx.annotation.RequiresApi +import androidx.preference.PreferenceManager +import com.github.gotify.client.model.Application import com.github.gotify.log.Log internal object NotificationSupport { @RequiresApi(Build.VERSION_CODES.O) - fun createChannels(notificationManager: NotificationManager) { - try { - // Low importance so that persistent notification can be sorted towards bottom of - // notification shade. Also prevents vibrations caused by persistent notification - val foreground = NotificationChannel( - Channel.FOREGROUND, - "Gotify foreground notification", - NotificationManager.IMPORTANCE_LOW - ) - foreground.setShowBadge(false) + fun createForegroundChannel(context: Context, notificationManager: NotificationManager) { + // Low importance so that persistent notification can be sorted towards bottom of + // notification shade. Also prevents vibrations caused by persistent notification + val foreground = NotificationChannel( + Channel.FOREGROUND, + context.getString(R.string.notification_channel_title_foreground), + NotificationManager.IMPORTANCE_LOW + ).apply { + setShowBadge(false) + } + notificationManager.createNotificationChannel(foreground) + } + @RequiresApi(Build.VERSION_CODES.O) + fun createChannels( + context: Context, + notificationManager: NotificationManager, + applications: List + ) { + if (areAppChannelsRequested(context)) { + notificationManager.notificationChannels.forEach { channel -> + if (channel.id != Channel.FOREGROUND) { + notificationManager.deleteNotificationChannel(channel.id) + } + } + applications.forEach { app -> + createAppChannels(context, notificationManager, app.id.toString(), app.name) + } + } else { + notificationManager.notificationChannelGroups.forEach { group -> + notificationManager.deleteNotificationChannelGroup(group.id) + } + createGeneralChannels(context, notificationManager) + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createGeneralChannels( + context: Context, + notificationManager: NotificationManager + ) { + try { val messagesImportanceMin = NotificationChannel( Channel.MESSAGES_IMPORTANCE_MIN, - "Min priority messages (<1)", + context.getString(R.string.notification_channel_title_min), NotificationManager.IMPORTANCE_MIN ) val messagesImportanceLow = NotificationChannel( Channel.MESSAGES_IMPORTANCE_LOW, - "Low priority messages (1-3)", + context.getString(R.string.notification_channel_title_low), NotificationManager.IMPORTANCE_LOW ) val messagesImportanceDefault = NotificationChannel( Channel.MESSAGES_IMPORTANCE_DEFAULT, - "Normal priority messages (4-7)", + context.getString(R.string.notification_channel_title_normal), NotificationManager.IMPORTANCE_DEFAULT - ) - messagesImportanceDefault.enableLights(true) - messagesImportanceDefault.lightColor = Color.CYAN - messagesImportanceDefault.enableVibration(true) + ).apply { + enableLights(true) + lightColor = Color.CYAN + enableVibration(true) + } val messagesImportanceHigh = NotificationChannel( Channel.MESSAGES_IMPORTANCE_HIGH, - "High priority messages (>7)", + context.getString(R.string.notification_channel_title_high), NotificationManager.IMPORTANCE_HIGH - ) - messagesImportanceHigh.enableLights(true) - messagesImportanceHigh.lightColor = Color.CYAN - messagesImportanceHigh.enableVibration(true) + ).apply { + enableLights(true) + lightColor = Color.CYAN + enableVibration(true) + } - notificationManager.createNotificationChannel(foreground) notificationManager.createNotificationChannel(messagesImportanceMin) notificationManager.createNotificationChannel(messagesImportanceLow) notificationManager.createNotificationChannel(messagesImportanceDefault) @@ -60,6 +96,88 @@ internal object NotificationSupport { } } + @RequiresApi(api = Build.VERSION_CODES.O) + fun createChannelIfNonexistent( + context: Context, + notificationManager: NotificationManager, + groupId: String, + groupName: String, + channelId: String + ) { + if (!doesNotificationChannelExist(context, channelId)) { + createAppChannels(context, notificationManager, groupId, groupName) + } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createAppChannels( + context: Context, + notificationManager: NotificationManager, + groupId: String, + groupName: String + ) { + try { + notificationManager.createNotificationChannelGroup( + NotificationChannelGroup( + groupId, + groupName + ) + ) + + val messagesImportanceMin = NotificationChannel( + getChannelID(Channel.MESSAGES_IMPORTANCE_MIN, groupId), + context.getString(R.string.notification_channel_title_min), + NotificationManager.IMPORTANCE_MIN + ).apply { + group = groupId + } + + val messagesImportanceLow = NotificationChannel( + getChannelID(Channel.MESSAGES_IMPORTANCE_LOW, groupId), + context.getString(R.string.notification_channel_title_low), + NotificationManager.IMPORTANCE_LOW + ).apply { + group = groupId + } + + val messagesImportanceDefault = NotificationChannel( + getChannelID(Channel.MESSAGES_IMPORTANCE_DEFAULT, groupId), + context.getString(R.string.notification_channel_title_normal), + NotificationManager.IMPORTANCE_DEFAULT + ).apply { + enableLights(true) + lightColor = Color.CYAN + enableVibration(true) + group = groupId + } + + val messagesImportanceHigh = NotificationChannel( + getChannelID(Channel.MESSAGES_IMPORTANCE_HIGH, groupId), + context.getString(R.string.notification_channel_title_high), + NotificationManager.IMPORTANCE_HIGH + ).apply { + enableLights(true) + lightColor = Color.CYAN + enableVibration(true) + group = groupId + } + + notificationManager.createNotificationChannel(messagesImportanceMin) + notificationManager.createNotificationChannel(messagesImportanceLow) + notificationManager.createNotificationChannel(messagesImportanceDefault) + notificationManager.createNotificationChannel(messagesImportanceHigh) + } catch (e: Exception) { + Log.e("Could not create channel", e) + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private fun doesNotificationChannelExist(context: Context, channelId: String): Boolean { + val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = manager.getNotificationChannel(channelId) + return channel != null + } + /** * Map {@link com.github.gotify.client.model.Message#getPriority() Gotify message priorities to * Android channels. @@ -87,6 +205,21 @@ internal object NotificationSupport { } } + private fun getChannelID(importance: String, groupId: String): String { + return "$importance::$groupId" + } + + fun getChannelID(priority: Long, groupId: String): String { + return getChannelID(convertPriorityToChannel(priority), groupId) + } + + fun areAppChannelsRequested(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + context.getString(R.string.setting_key_notification_channels), + context.resources.getBoolean(R.bool.notification_channels) + ) + } + object Group { const val MESSAGES = "GOTIFY_GROUP_MESSAGES" } diff --git a/app/src/main/kotlin/com/github/gotify/init/InitializationActivity.kt b/app/src/main/kotlin/com/github/gotify/init/InitializationActivity.kt index 49e6d1f..c9d68be 100644 --- a/app/src/main/kotlin/com/github/gotify/init/InitializationActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/init/InitializationActivity.kt @@ -2,6 +2,7 @@ package com.github.gotify.init import android.Manifest import android.app.NotificationManager +import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle @@ -41,8 +42,9 @@ internal class InitializationActivity : AppCompatActivity() { ThemeHelper.setTheme(this, theme) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationSupport.createChannels( - this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager + NotificationSupport.createForegroundChannel( + this, + (this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager) ) } UncaughtExceptionHandler.registerCurrentThread() diff --git a/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt b/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt index 60d89d9..cecfec0 100644 --- a/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt @@ -9,6 +9,7 @@ import android.graphics.Canvas import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -28,6 +29,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.gotify.BuildConfig import com.github.gotify.MissedMessageUtil +import com.github.gotify.NotificationSupport import com.github.gotify.R import com.github.gotify.Utils import com.github.gotify.Utils.launchCoroutine @@ -204,6 +206,14 @@ internal class MessagesActivity : .into(t) } selectAppInMenu(selectedItem) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationSupport.createChannels( + this, + (this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager), + applications + ) + } } private fun initDrawer() { diff --git a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt index a0c20c9..2d6a98c 100644 --- a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt +++ b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt @@ -5,6 +5,7 @@ import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.app.Service +import android.content.Context import android.content.Intent import android.content.IntentFilter import android.graphics.Color @@ -312,13 +313,26 @@ internal class WebSocketService : Service() { PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - val b = NotificationCompat.Builder( - this, - NotificationSupport.convertPriorityToChannel(priority) - ) + val channelId: String + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + NotificationSupport.areAppChannelsRequested(this) + ) { + channelId = NotificationSupport.getChannelID(priority, appId.toString()) + NotificationSupport.createChannelIfNonexistent( + this, + (this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager), + appId.toString(), + appId.toString(), + channelId + ) + } else { + channelId = NotificationSupport.convertPriorityToChannel(priority) + } + + val b = NotificationCompat.Builder(this, channelId) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - showNotificationGroup(priority) + showNotificationGroup(channelId) } b.setAutoCancel(true) @@ -365,7 +379,7 @@ internal class WebSocketService : Service() { } @RequiresApi(Build.VERSION_CODES.N) - fun showNotificationGroup(priority: Long) { + fun showNotificationGroup(channelId: String) { val intent = Intent(this, MessagesActivity::class.java) val contentIntent = PendingIntent.getActivity( this, @@ -376,7 +390,7 @@ internal class WebSocketService : Service() { val builder = NotificationCompat.Builder( this, - NotificationSupport.convertPriorityToChannel(priority) + channelId ) builder.setAutoCancel(true) diff --git a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt index bb4a825..ece7b6c 100644 --- a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt @@ -5,6 +5,7 @@ import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.os.Build import android.os.Bundle import android.view.MenuItem import android.view.View @@ -14,6 +15,7 @@ import androidx.preference.ListPreferenceDialogFragmentCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager +import androidx.preference.SwitchPreferenceCompat import com.github.gotify.R import com.github.gotify.databinding.SettingsActivityBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -48,17 +50,36 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - if (getString(R.string.setting_key_theme) == key) { - ThemeHelper.setTheme( - this, - sharedPreferences.getString(key, getString(R.string.theme_default))!! - ) + when (key) { + getString(R.string.setting_key_theme) -> { + ThemeHelper.setTheme( + this, + sharedPreferences.getString(key, getString(R.string.theme_default))!! + ) + } + getString(R.string.setting_key_notification_channels) -> { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + + val dialogBuilder = MaterialAlertDialogBuilder(this) + .setTitle(R.string.setting_notification_channels_dialog_title) + .setMessage(R.string.setting_notification_channels_dialog_message) + .setPositiveButton(android.R.string.ok, null) + + if (!isFinishing) { + dialogBuilder.show() + } + } } } class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + findPreference( + getString(R.string.setting_key_notification_channels) + )?.isEnabled = true + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index a4ef6f8..92ca38b 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -34,4 +34,5 @@ time_format_absolute time_format_relative + false diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7310c44..d8fc202 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,11 @@ Later Time format time_format + Notifications + Separate app notification channels + notification_channels + Manual refresh required + For the change to take effect you must manually refresh the apps using the refresh button in the main menu. Push message App: Priority: @@ -97,4 +102,10 @@ in %d minute in %d minutes + + Gotify foreground notification + Min priority messages (<1) + Low priority messages (1–3) + Normal priority messages (4–7) + High priority messages (>7) diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 0971135..add33e0 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -24,4 +24,13 @@ android:title="@string/setting_time_format" /> + + + + From aea9c0c3467b7d3f72101e54f08752366369e38e Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Mon, 13 Feb 2023 12:30:09 +0100 Subject: [PATCH 2/9] Apply Material 3 design to preference switch --- app/src/main/res/layout/preference_switch.xml | 6 ++++++ app/src/main/res/values/styles.xml | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/layout/preference_switch.xml diff --git a/app/src/main/res/layout/preference_switch.xml b/app/src/main/res/layout/preference_switch.xml new file mode 100644 index 0000000..a9b967f --- /dev/null +++ b/app/src/main/res/layout/preference_switch.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ac2ad10..b034e1e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + + From 544c960bff525385463d1941ef2958bd3c803eca Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Mon, 13 Feb 2023 16:55:36 +0100 Subject: [PATCH 3/9] Fix unexpected settings dialog behaviour --- .../gotify/settings/SettingsActivity.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt index ece7b6c..e60877b 100644 --- a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt @@ -57,18 +57,6 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL sharedPreferences.getString(key, getString(R.string.theme_default))!! ) } - getString(R.string.setting_key_notification_channels) -> { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - - val dialogBuilder = MaterialAlertDialogBuilder(this) - .setTitle(R.string.setting_notification_channels_dialog_title) - .setMessage(R.string.setting_notification_channels_dialog_message) - .setPositiveButton(android.R.string.ok, null) - - if (!isFinishing) { - dialogBuilder.show() - } - } } } @@ -86,6 +74,8 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL super.onViewCreated(view, savedInstanceState) val messageLayout: ListPreference? = findPreference(getString(R.string.setting_key_message_layout)) + val notificationChannels: SwitchPreferenceCompat? = + findPreference(getString(R.string.setting_key_notification_channels)) messageLayout?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> MaterialAlertDialogBuilder(requireContext()) @@ -103,6 +93,20 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL .show() true } + notificationChannels?.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, _ -> + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return@OnPreferenceChangeListener true + } + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.setting_notification_channels_dialog_title) + .setMessage(R.string.setting_notification_channels_dialog_message) + .setPositiveButton(android.R.string.ok, null) + .show() + + true + } } override fun onDisplayPreferenceDialog(preference: Preference) { From 9d2622fbc6fe1d827422bbd3b18bce4046be5294 Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Fri, 17 Feb 2023 15:40:13 +0100 Subject: [PATCH 4/9] Show restart dialog on setting change --- .../gotify/settings/SettingsActivity.kt | 35 ++++++++----------- app/src/main/res/values/strings.xml | 10 +++--- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt index e60877b..eebe0d9 100644 --- a/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/settings/SettingsActivity.kt @@ -78,33 +78,15 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL findPreference(getString(R.string.setting_key_notification_channels)) messageLayout?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.setting_message_layout_dialog_title) - .setMessage(R.string.setting_message_layout_dialog_message) - .setPositiveButton( - getString(R.string.setting_message_layout_dialog_button1) - ) { _, _ -> - restartApp() - } - .setNegativeButton( - getString(R.string.setting_message_layout_dialog_button2), - null - ) - .show() + showRestartDialog() true } notificationChannels?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - return@OnPreferenceChangeListener true + return@OnPreferenceChangeListener false } - - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.setting_notification_channels_dialog_title) - .setMessage(R.string.setting_notification_channels_dialog_message) - .setPositiveButton(android.R.string.ok, null) - .show() - + showRestartDialog() true } } @@ -127,6 +109,17 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL ) } + private fun showRestartDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.setting_restart_dialog_title) + .setMessage(R.string.setting_restart_dialog_message) + .setPositiveButton(getString(R.string.setting_restart_dialog_button1)) { _, _ -> + restartApp() + } + .setNegativeButton(getString(R.string.setting_restart_dialog_button2), null) + .show() + } + private fun restartApp() { val packageManager = requireContext().packageManager val packageName = requireContext().packageName diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8fc202..d539e49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,17 +74,15 @@ theme Message layout message_layout - Restart App? - The change will be effective on next app start.\n\nDo you want to restart now? - Restart - Later + Restart App? + The change will be effective on next app start.\n\nDo you want to restart now? + Restart + Later Time format time_format Notifications Separate app notification channels notification_channels - Manual refresh required - For the change to take effect you must manually refresh the apps using the refresh button in the main menu. Push message App: Priority: From 38bd1b5e32ca38c336435871bca4efd392633e36 Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Fri, 17 Feb 2023 15:42:00 +0100 Subject: [PATCH 5/9] Switch channel id components --- app/src/main/kotlin/com/github/gotify/NotificationSupport.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt index cfebb3b..92bceea 100644 --- a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt +++ b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt @@ -206,7 +206,7 @@ internal object NotificationSupport { } private fun getChannelID(importance: String, groupId: String): String { - return "$importance::$groupId" + return "$groupId::$importance" } fun getChannelID(priority: Long, groupId: String): String { From 5399d003e40fc1068e5f69814bbf2b120cc44096 Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Fri, 17 Feb 2023 15:45:41 +0100 Subject: [PATCH 6/9] Shrink signature of channel fallback creation method --- .../main/kotlin/com/github/gotify/NotificationSupport.kt | 7 ++++--- .../kotlin/com/github/gotify/service/WebSocketService.kt | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt index 92bceea..dd27c22 100644 --- a/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt +++ b/app/src/main/kotlin/com/github/gotify/NotificationSupport.kt @@ -3,6 +3,7 @@ package com.github.gotify import android.app.NotificationChannel import android.app.NotificationChannelGroup import android.app.NotificationManager +import android.app.Service import android.content.Context import android.graphics.Color import android.os.Build @@ -99,13 +100,13 @@ internal object NotificationSupport { @RequiresApi(api = Build.VERSION_CODES.O) fun createChannelIfNonexistent( context: Context, - notificationManager: NotificationManager, groupId: String, - groupName: String, channelId: String ) { if (!doesNotificationChannelExist(context, channelId)) { - createAppChannels(context, notificationManager, groupId, groupName) + val notificationManager = (context as Service) + .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + createAppChannels(context, notificationManager, groupId, groupId) } } diff --git a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt index 2d6a98c..11372b0 100644 --- a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt +++ b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt @@ -5,7 +5,6 @@ import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.app.Service -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.graphics.Color @@ -320,8 +319,6 @@ internal class WebSocketService : Service() { channelId = NotificationSupport.getChannelID(priority, appId.toString()) NotificationSupport.createChannelIfNonexistent( this, - (this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager), - appId.toString(), appId.toString(), channelId ) From 1182f358cba3f450b5ed3413840e2dc46af39bd4 Mon Sep 17 00:00:00 2001 From: Niko Diamadis Date: Sat, 18 Feb 2023 12:39:55 +0100 Subject: [PATCH 7/9] Move channel creation to WebSocketService --- .../gotify/messages/MessagesActivity.kt | 10 ----- .../messages/provider/MessageImageCombiner.kt | 2 +- .../github/gotify/picasso/PicassoHandler.kt | 25 +++--------- .../github/gotify/service/WebSocketService.kt | 38 ++++++++++++++++++- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt b/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt index cecfec0..60d89d9 100644 --- a/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt +++ b/app/src/main/kotlin/com/github/gotify/messages/MessagesActivity.kt @@ -9,7 +9,6 @@ import android.graphics.Canvas import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -29,7 +28,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.gotify.BuildConfig import com.github.gotify.MissedMessageUtil -import com.github.gotify.NotificationSupport import com.github.gotify.R import com.github.gotify.Utils import com.github.gotify.Utils.launchCoroutine @@ -206,14 +204,6 @@ internal class MessagesActivity : .into(t) } selectAppInMenu(selectedItem) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationSupport.createChannels( - this, - (this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager), - applications - ) - } } private fun initDrawer() { diff --git a/app/src/main/kotlin/com/github/gotify/messages/provider/MessageImageCombiner.kt b/app/src/main/kotlin/com/github/gotify/messages/provider/MessageImageCombiner.kt index b5d2750..ae2b8bf 100644 --- a/app/src/main/kotlin/com/github/gotify/messages/provider/MessageImageCombiner.kt +++ b/app/src/main/kotlin/com/github/gotify/messages/provider/MessageImageCombiner.kt @@ -9,7 +9,7 @@ internal object MessageImageCombiner { return messages.map { MessageWithImage(message = it, image = appIdToImage[it.appid]) } } - fun appIdToImage(applications: List): Map { + private fun appIdToImage(applications: List): Map { val map = mutableMapOf() applications.forEach { map[it.id] = it.image diff --git a/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt b/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt index 2ece693..0cfa587 100644 --- a/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt +++ b/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt @@ -6,12 +6,9 @@ import android.graphics.BitmapFactory import com.github.gotify.R import com.github.gotify.Settings import com.github.gotify.Utils -import com.github.gotify.api.Callback import com.github.gotify.api.CertUtils -import com.github.gotify.api.ClientFactory -import com.github.gotify.client.api.ApplicationApi +import com.github.gotify.client.model.Application import com.github.gotify.log.Log -import com.github.gotify.messages.provider.MessageImageCombiner import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso import okhttp3.Cache @@ -32,7 +29,7 @@ internal class PicassoHandler(private val context: Context, private val settings ) private val picasso = makePicasso() - private val appIdToAppImage = ConcurrentHashMap() + private val appIdToApp = ConcurrentHashMap() private fun makePicasso(): Picasso { val builder = OkHttpClient.Builder() @@ -54,7 +51,7 @@ internal class PicassoHandler(private val context: Context, private val settings } try { return getImageFromUrl( - Utils.resolveAbsoluteUrl("${settings.url}/", appIdToAppImage[appId]) + Utils.resolveAbsoluteUrl("${settings.url}/", appIdToApp[appId]?.image) ) } catch (e: IOException) { Log.e("Could not load image for notification", e) @@ -62,19 +59,9 @@ internal class PicassoHandler(private val context: Context, private val settings return BitmapFactory.decodeResource(context.resources, R.drawable.gotify) } - fun updateAppIds() { - ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token) - .createService(ApplicationApi::class.java) - .apps - .enqueue( - Callback.call( - onSuccess = Callback.SuccessBody { apps -> - appIdToAppImage.clear() - appIdToAppImage.putAll(MessageImageCombiner.appIdToImage(apps)) - }, - onError = { appIdToAppImage.clear() } - ) - ) + fun updateApps(apps: List) { + appIdToApp.clear() + appIdToApp.putAll(apps.associateBy { it.id }) } fun get() = picasso diff --git a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt index 11372b0..4893f78 100644 --- a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt +++ b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt @@ -23,7 +23,9 @@ import com.github.gotify.Settings import com.github.gotify.Utils import com.github.gotify.api.Callback import com.github.gotify.api.ClientFactory +import com.github.gotify.client.api.ApplicationApi import com.github.gotify.client.api.MessageApi +import com.github.gotify.client.model.Application import com.github.gotify.client.model.Message import com.github.gotify.log.Log import com.github.gotify.log.UncaughtExceptionHandler @@ -111,7 +113,41 @@ internal class WebSocketService : Service() { val intentFilter = IntentFilter() intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) - picassoHandler.updateAppIds() + fetchAppIds( + onSuccess = { apps -> + picassoHandler.updateApps(apps) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannels(apps) + } + }, + onError = { picassoHandler.updateApps(listOf()) } + ) + } + + private fun fetchAppIds( + onSuccess: (apps: List) -> Unit, + onError: () -> Unit + ) { + ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token) + .createService(ApplicationApi::class.java) + .apps + .enqueue( + Callback.call( + onSuccess = Callback.SuccessBody { apps -> + onSuccess(apps) + }, + onError = { onError() } + ) + ) + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createNotificationChannels(apps: List) { + NotificationSupport.createChannels( + this, + (this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager), + apps + ) } private fun onClose() { From cfc473cce0ec257004d3543d00d73b8104d30016 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Tue, 21 Feb 2023 13:31:48 +0100 Subject: [PATCH 8/9] Move appIdToApp to WebSocketService The PicassoHandler doesn't really need access to the full list, as it only requires the application image. --- .../github/gotify/picasso/PicassoHandler.kt | 13 ++---- .../github/gotify/service/WebSocketService.kt | 40 +++++++------------ 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt b/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt index 0cfa587..bddc3a4 100644 --- a/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt +++ b/app/src/main/kotlin/com/github/gotify/picasso/PicassoHandler.kt @@ -15,7 +15,6 @@ import okhttp3.Cache import okhttp3.OkHttpClient import java.io.File import java.io.IOException -import java.util.concurrent.ConcurrentHashMap internal class PicassoHandler(private val context: Context, private val settings: Settings) { companion object { @@ -29,7 +28,6 @@ internal class PicassoHandler(private val context: Context, private val settings ) private val picasso = makePicasso() - private val appIdToApp = ConcurrentHashMap() private fun makePicasso(): Picasso { val builder = OkHttpClient.Builder() @@ -45,13 +43,13 @@ internal class PicassoHandler(private val context: Context, private val settings @Throws(IOException::class) fun getImageFromUrl(url: String?): Bitmap = picasso.load(url).get() - fun getIcon(appId: Long): Bitmap { - if (appId == -1L) { + fun getIcon(app: Application?): Bitmap { + if (app == null) { return BitmapFactory.decodeResource(context.resources, R.drawable.gotify) } try { return getImageFromUrl( - Utils.resolveAbsoluteUrl("${settings.url}/", appIdToApp[appId]?.image) + Utils.resolveAbsoluteUrl("${settings.url}/", app.image) ) } catch (e: IOException) { Log.e("Could not load image for notification", e) @@ -59,11 +57,6 @@ internal class PicassoHandler(private val context: Context, private val settings return BitmapFactory.decodeResource(context.resources, R.drawable.gotify) } - fun updateApps(apps: List) { - appIdToApp.clear() - appIdToApp.putAll(apps.associateBy { it.id }) - } - fun get() = picasso @Throws(IOException::class) diff --git a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt index 4893f78..2a33235 100644 --- a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt +++ b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt @@ -33,6 +33,7 @@ import com.github.gotify.messages.Extras import com.github.gotify.messages.MessagesActivity import com.github.gotify.picasso.PicassoHandler import io.noties.markwon.Markwon +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong internal class WebSocketService : Service() { @@ -43,6 +44,7 @@ internal class WebSocketService : Service() { private lateinit var settings: Settings private var connection: WebSocketConnection? = null + private val appIdToApp = ConcurrentHashMap() private val lastReceivedMessage = AtomicLong(NOT_LOADED) private lateinit var missingMessageUtil: MissedMessageUtil @@ -113,43 +115,31 @@ internal class WebSocketService : Service() { val intentFilter = IntentFilter() intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) - fetchAppIds( - onSuccess = { apps -> - picassoHandler.updateApps(apps) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannels(apps) - } - }, - onError = { picassoHandler.updateApps(listOf()) } - ) + fetchApps() } - private fun fetchAppIds( - onSuccess: (apps: List) -> Unit, - onError: () -> Unit - ) { + private fun fetchApps() { ClientFactory.clientToken(settings.url, settings.sslSettings(), settings.token) .createService(ApplicationApi::class.java) .apps .enqueue( Callback.call( onSuccess = Callback.SuccessBody { apps -> - onSuccess(apps) + appIdToApp.clear() + appIdToApp.putAll(apps.associateBy { it.id }) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationSupport.createChannels( + this, + (this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager), + apps + ) + } }, - onError = { onError() } + onError = { appIdToApp.clear() } ) ) } - @RequiresApi(Build.VERSION_CODES.O) - private fun createNotificationChannels(apps: List) { - NotificationSupport.createChannels( - this, - (this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager), - apps - ) - } - private fun onClose() { showForegroundNotification( getString(R.string.websocket_closed), @@ -372,7 +362,7 @@ internal class WebSocketService : Service() { .setDefaults(Notification.DEFAULT_ALL) .setWhen(System.currentTimeMillis()) .setSmallIcon(R.drawable.ic_gotify) - .setLargeIcon(picassoHandler.getIcon(appId)) + .setLargeIcon(picassoHandler.getIcon(appIdToApp[appId])) .setTicker("${getString(R.string.app_name)} - $title") .setGroup(NotificationSupport.Group.MESSAGES) .setContentTitle(title) From 50f4dea1ac7c34ce92572e55d59ce4cc62c5ca36 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Tue, 21 Feb 2023 13:48:26 +0100 Subject: [PATCH 9/9] Remove unused intent filter --- .../main/kotlin/com/github/gotify/service/WebSocketService.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt index 2a33235..eb331b4 100644 --- a/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt +++ b/app/src/main/kotlin/com/github/gotify/service/WebSocketService.kt @@ -6,7 +6,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Intent -import android.content.IntentFilter import android.graphics.Color import android.net.ConnectivityManager import android.net.Uri @@ -112,9 +111,6 @@ internal class WebSocketService : Service() { .onReconnected { notifyMissedNotifications() } .start() - val intentFilter = IntentFilter() - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) - fetchApps() }