Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/java/io/heckel/ntfy/backup/Backuper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class Backuper(val context: Context) {
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,
overrideVolumeMaxPriority = s.overrideVolumeMaxPriority ?: Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL,
overrideVolumeSetting = s.overrideVolumeSetting ?: Repository.OVERRIDE_VOLUME_SETTING_DEFAULT,
lastNotificationId = s.lastNotificationId,
icon = s.icon,
upAppId = s.upAppId,
Expand Down Expand Up @@ -255,6 +257,8 @@ class Backuper(val context: Context) {
minPriority = s.minPriority,
autoDelete = s.autoDelete,
insistent = s.insistent,
overrideVolumeMaxPriority = s.overrideVolumeMaxPriority,
overrideVolumeSetting = s.overrideVolumeSetting,
lastNotificationId = s.lastNotificationId,
icon = s.icon,
upAppId = s.upAppId,
Expand Down Expand Up @@ -375,6 +379,8 @@ data class Subscription(
val minPriority: Int?,
val autoDelete: Long?,
val insistent: Int?,
val overrideVolumeMaxPriority: Int?,
val overrideVolumeSetting: Int?,
val lastNotificationId: String?,
val icon: String?,
val upAppId: String?,
Expand Down
28 changes: 22 additions & 6 deletions app/src/main/java/io/heckel/ntfy/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ data class Subscription(
@ColumnInfo(name = "minPriority") val minPriority: Int,
@ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds
@ColumnInfo(name = "insistent") val insistent: Int, // Ring constantly for max priority notifications (-1 = use global, 0 = off, 1 = on)
@ColumnInfo(name = "overrideVolumeMaxPriority") val overrideVolumeMaxPriority: Int, // Override Volume for max priority notifications (-1 = use global, 0 = off, 1 = on)
@ColumnInfo(name = "overrideVolumeSetting") val overrideVolumeSetting: Int,
@ColumnInfo(name = "lastNotificationId") val lastNotificationId: String?, // Used for polling, with since=<id>
@ColumnInfo(name = "icon") val icon: String?, // content://-URI (or later other identifier)
@ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name
Expand All @@ -39,6 +41,8 @@ data class Subscription(
minPriority: Int,
autoDelete: Long,
insistent: Int,
overrideVolumeMaxPriority: Int,
overrideVolumeSetting: Int,
lastNotificationId: String,
icon: String,
upAppId: String,
Expand All @@ -55,6 +59,8 @@ data class Subscription(
minPriority,
autoDelete,
insistent,
overrideVolumeMaxPriority,
overrideVolumeSetting,
lastNotificationId,
icon,
upAppId,
Expand All @@ -81,6 +87,8 @@ data class SubscriptionWithMetadata(
val autoDelete: Long,
val minPriority: Int,
val insistent: Int,
val overrideVolumeMaxPriority: Int,
val overrideVolumeSetting: Int,
val lastNotificationId: String?,
val icon: String?,
val upAppId: String?,
Expand Down Expand Up @@ -201,7 +209,7 @@ data class LogEntry(
this(0, timestamp, tag, level, message, exception)
}

@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 14)
@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 15)
@TypeConverters(Converters::class)
abstract class Database : RoomDatabase() {
abstract fun subscriptionDao(): SubscriptionDao
Expand Down Expand Up @@ -230,6 +238,7 @@ abstract class Database : RoomDatabase() {
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_12_13)
.addMigrations(MIGRATION_13_14)
.addMigrations(MIGRATION_14_15)
.fallbackToDestructiveMigration()
.build()
this.instance = instance
Expand Down Expand Up @@ -341,14 +350,21 @@ abstract class Database : RoomDatabase() {
db.execSQL("ALTER TABLE Notification ADD COLUMN contentType TEXT NOT NULL DEFAULT ('')")
}
}

private val MIGRATION_14_15 = object : Migration(14, 15) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Subscription ADD COLUMN overrideVolumeMaxPriority INTEGER NOT NULL DEFAULT (-1)") // = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL
db.execSQL("ALTER TABLE Subscription ADD COLUMN overrideVolumeSetting INTEGER NOT NULL DEFAULT (7)")
}
}
}
}

@Dao
interface SubscriptionDao {
@Query("""
SELECT
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, s.dedicatedChannels,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
Expand All @@ -361,7 +377,7 @@ interface SubscriptionDao {

@Query("""
SELECT
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, s.dedicatedChannels,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
Expand All @@ -374,7 +390,7 @@ interface SubscriptionDao {

@Query("""
SELECT
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, s.dedicatedChannels,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
Expand All @@ -387,7 +403,7 @@ interface SubscriptionDao {

@Query("""
SELECT
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, s.dedicatedChannels,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
Expand All @@ -400,7 +416,7 @@ interface SubscriptionDao {

@Query("""
SELECT
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, s.dedicatedChannels,
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.overrideVolumeMaxPriority, s.overrideVolumeSetting, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, s.dedicatedChannels,
COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive
Expand Down
52 changes: 52 additions & 0 deletions app/src/main/java/io/heckel/ntfy/db/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.os.Build
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.*
import io.heckel.ntfy.ui.SettingsActivity
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.validUrl
import java.util.concurrent.ConcurrentHashMap
Expand Down Expand Up @@ -302,6 +303,37 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
.apply()
}

fun getOverrideVolumeMaxPriorityEnabled(): Boolean {
return sharedPrefs.getBoolean(SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED, false)
}

fun setOverrideVolumeMaxPriorityEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED, enabled)
.apply()
}

fun getOverrideVolumeSetting(): Int {
return sharedPrefs.getInt(SHARED_PREFS_OVERRIDE_VOLUME_SETTING, 7)
}

fun setOverrideVolumeSetting(value: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_OVERRIDE_VOLUME_SETTING, value)
.apply()
}

fun getPreviousVolume(): Int {
return sharedPrefs.getInt(SHARED_PREFS_PREVIOUS_VOLUME, 7)
}

fun setPreviousVolume(previousVolume: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_PREVIOUS_VOLUME, previousVolume)
.apply()

}

fun getInsistentMaxPriorityEnabled(): Boolean {
return sharedPrefs.getBoolean(SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED, false) // Disabled by default
}
Expand Down Expand Up @@ -424,6 +456,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
minPriority = s.minPriority,
autoDelete = s.autoDelete,
insistent = s.insistent,
overrideVolumeMaxPriority = s.overrideVolumeMaxPriority,
overrideVolumeSetting = s.overrideVolumeSetting,
lastNotificationId = s.lastNotificationId,
icon = s.icon,
upAppId = s.upAppId,
Expand Down Expand Up @@ -451,6 +485,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
minPriority = s.minPriority,
autoDelete = s.autoDelete,
insistent = s.insistent,
overrideVolumeMaxPriority = s.overrideVolumeMaxPriority,
overrideVolumeSetting = s.overrideVolumeSetting,
lastNotificationId = s.lastNotificationId,
icon = s.icon,
upAppId = s.upAppId,
Expand Down Expand Up @@ -485,6 +521,14 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
return connectionStatesLiveData.value!!.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE }
}

fun registerOnSharedPreferenceChangeListener(settingsActivity: SettingsActivity) {
sharedPrefs.registerOnSharedPreferenceChangeListener(settingsActivity)
}

fun unregisterOnSharedPreferenceChangeListener(settingsActivity: SettingsActivity) {
sharedPrefs.unregisterOnSharedPreferenceChangeListener(settingsActivity)
}

companion object {
const val SHARED_PREFS_ID = "MainPreferences"
const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion"
Expand All @@ -499,6 +543,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
const val SHARED_PREFS_UNIFIEDPUSH_ENABLED = "UnifiedPushEnabled"
const val SHARED_PREFS_INSISTENT_MAX_PRIORITY_ENABLED = "InsistentMaxPriority"
const val SHARED_PREFS_OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED = "OverrideVolumeMaxPriority"
const val SHARED_PREFS_OVERRIDE_VOLUME_SETTING = "OverrideVolumeSetting"
const val SHARED_PREFS_PREVIOUS_VOLUME = "PreviousVolume"
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)
Expand Down Expand Up @@ -534,6 +581,11 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml
const val INSISTENT_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code)

const val OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml
const val OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code)

const val OVERRIDE_VOLUME_SETTING_DEFAULT = 7

const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp"
const val CONNECTION_PROTOCOL_WS = "ws"

Expand Down
59 changes: 44 additions & 15 deletions app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
import android.net.Uri
import android.net.rtp.AudioStream
import android.os.Build
import android.os.Bundle
import android.text.SpannedString
Expand Down Expand Up @@ -94,8 +95,15 @@ class NotificationService(val context: Context) {
val title = formatTitle(subscription, notification)
val groupId = if (subscription.dedicatedChannels) subscriptionGroupId(subscription) else DEFAULT_GROUP
val channelId = toChannelId(groupId, notification.priority)
val overrideVolume = notification.priority == PRIORITY_MAX &&
((repository.getOverrideVolumeMaxPriorityEnabled() && subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL) || subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED)
val volumeSetting = if (subscription.overrideVolumeMaxPriority == Repository.OVERRIDE_VOLUME_MAX_PRIORITY_ENABLED) {
subscription.overrideVolumeSetting
} else {
repository.getOverrideVolumeSetting()
}
val insistent = notification.priority == PRIORITY_MAX &&
(repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED)
((repository.getInsistentMaxPriorityEnabled() && subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL) || 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)))
Expand All @@ -115,7 +123,7 @@ class NotificationService(val context: Context) {

maybeCreateNotificationGroup(groupId, subscriptionGroupName(subscription))
maybeCreateNotificationChannel(groupId, notification.priority)
maybePlayInsistentSound(groupId, insistent)
maybeOverrideVolumeAndPlayInsistentSound(groupId, overrideVolume, volumeSetting, insistent)

notificationManager.notify(notification.notificationId, builder.build())
}
Expand Down Expand Up @@ -355,8 +363,15 @@ 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
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
var audioStream = AudioManager.STREAM_NOTIFICATION
if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0){
audioStream = AudioManager.STREAM_ALARM
}
val repository = Repository.getInstance(context)
val mediaPlayer = repository.mediaPlayer
mediaPlayer.stop()
audioManager.setStreamVolume(audioStream, repository.getPreviousVolume(), 0)
}
}

Expand Down Expand Up @@ -443,24 +458,38 @@ class NotificationService(val context: Context) {
}
}

private fun maybePlayInsistentSound(groupId: String, insistent: Boolean) {
if (!insistent) {
private fun maybeOverrideVolumeAndPlayInsistentSound(groupId: String, overrideVolume: Boolean, volumeSetting: Int, insistent: Boolean) {
if (!overrideVolume && !insistent) {
return
}
try {
val mediaPlayer = repository.mediaPlayer
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
Log.d(TAG, "Media player: Playing insistent alarm on alarm channel")
mediaPlayer.reset()
mediaPlayer.setDataSource(context, getInsistentSound(groupId))
mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build())
mediaPlayer.isLooping = true
mediaPlayer.prepare()
mediaPlayer.start()
var audioStream = AudioManager.STREAM_NOTIFICATION
var usageStream = AudioAttributes.USAGE_NOTIFICATION
if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0){
audioStream = AudioManager.STREAM_ALARM
usageStream = AudioAttributes.USAGE_ALARM
}

val mediaPlayer = repository.mediaPlayer
if (overrideVolume) {
val previousVolume = audioManager.getStreamVolume(audioStream)
audioManager.setStreamVolume(audioStream, volumeSetting, 0)
repository.setPreviousVolume(previousVolume)
}

mediaPlayer.reset()
mediaPlayer.setDataSource(context, getInsistentSound(groupId))
mediaPlayer.setAudioAttributes(AudioAttributes.Builder().setUsage(usageStream).build())
if (overrideVolume) {
Log.d(TAG, "Media player: Playing alarm on alarm channel")
mediaPlayer.setOnCompletionListener { audioManager.setStreamVolume(audioStream, repository.getPreviousVolume(), 0) }
} else {
Log.d(TAG, "Media player: Alarm volume is 0; not playing insistent alarm")
Log.d(TAG, "Media player: Playing insistent alarm on notification channel")
}
mediaPlayer.isLooping = insistent
mediaPlayer.prepare()
mediaPlayer.start()
} catch (e: Exception) {
Log.w(TAG, "Media player: Failed to play insistent alarm", e)
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL,
overrideVolumeMaxPriority = Repository.OVERRIDE_VOLUME_MAX_PRIORITY_USE_GLOBAL,
overrideVolumeSetting = Repository.OVERRIDE_VOLUME_SETTING_DEFAULT,
lastNotificationId = null,
icon = null,
upAppId = null,
Expand Down
Loading