Implement optional separate app notification channels

This commit is contained in:
Niko Diamadis
2023-02-13 12:24:40 +01:00
parent 8bae62cc24
commit ddd902e17e
8 changed files with 238 additions and 37 deletions

View File

@@ -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 {
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,
"Gotify foreground notification",
context.getString(R.string.notification_channel_title_foreground),
NotificationManager.IMPORTANCE_LOW
)
foreground.setShowBadge(false)
).apply {
setShowBadge(false)
}
notificationManager.createNotificationChannel(foreground)
}
@RequiresApi(Build.VERSION_CODES.O)
fun createChannels(
context: Context,
notificationManager: NotificationManager,
applications: List<Application>
) {
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"
}

View File

@@ -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()

View File

@@ -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() {

View File

@@ -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(
val channelId: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
NotificationSupport.areAppChannelsRequested(this)
) {
channelId = NotificationSupport.getChannelID(priority, appId.toString())
NotificationSupport.createChannelIfNonexistent(
this,
NotificationSupport.convertPriorityToChannel(priority)
(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)

View File

@@ -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) {
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<SwitchPreferenceCompat>(
getString(R.string.setting_key_notification_channels)
)?.isEnabled = true
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -34,4 +34,5 @@
</string-array>
<string name="time_format_value_absolute">time_format_absolute</string>
<string name="time_format_value_relative">time_format_relative</string>
<bool name="notification_channels">false</bool>
</resources>

View File

@@ -80,6 +80,11 @@
<string name="setting_message_layout_dialog_button2">Later</string>
<string name="setting_time_format">Time format</string>
<string name="setting_key_time_format">time_format</string>
<string name="setting_notifications">Notifications</string>
<string name="setting_notification_channels">Separate app notification channels</string>
<string name="setting_key_notification_channels">notification_channels</string>
<string name="setting_notification_channels_dialog_title">Manual refresh required</string>
<string name="setting_notification_channels_dialog_message">For the change to take effect you must manually refresh the apps using the refresh button in the main menu.</string>
<string name="push_message">Push message</string>
<string name="appListDescription">App:</string>
<string name="priorityDescription">Priority:</string>
@@ -97,4 +102,10 @@
<item quantity="one">in %d minute</item>
<item quantity="other">in %d minutes</item>
</plurals>
<string name="notification_channel_title_foreground">Gotify foreground notification</string>
<string name="notification_channel_title_min">Min priority messages (&lt;1)</string>
<string name="notification_channel_title_low">Low priority messages (13)</string>
<string name="notification_channel_title_normal">Normal priority messages (47)</string>
<string name="notification_channel_title_high">High priority messages (>7)</string>
</resources>

View File

@@ -24,4 +24,13 @@
android:title="@string/setting_time_format" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/setting_notifications" >
<SwitchPreferenceCompat
android:enabled="false"
android:defaultValue="@bool/notification_channels"
android:key="@string/setting_key_notification_channels"
android:title="@string/setting_notification_channels"
app:singleLineTitle="false" />
</PreferenceCategory>
</PreferenceScreen>