Works!
This commit is contained in:
parent
e18be4a2a7
commit
55ad2e65b5
9 changed files with 98 additions and 62 deletions
|
@ -134,7 +134,12 @@
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".msg.NotificationService$AlarmReceiver"/>
|
<!-- Broadcast receiver for when the notification is swiped away (currently only to cancel the insistent sound) -->
|
||||||
|
<receiver
|
||||||
|
android:name=".msg.NotificationService$DeleteBroadcastReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- Firebase messaging (note that this is empty in the F-Droid flavor) -->
|
<!-- Firebase messaging (note that this is empty in the F-Droid flavor) -->
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
package io.heckel.ntfy.app
|
package io.heckel.ntfy.app
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import io.heckel.ntfy.db.Database
|
|
||||||
import io.heckel.ntfy.db.Repository
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.Log
|
||||||
|
|
||||||
class Application : Application() {
|
class Application : Application() {
|
||||||
private val database by lazy {
|
|
||||||
Log.init(this) // What a hack, but this is super early and used everywhere
|
|
||||||
Database.getInstance(this)
|
|
||||||
}
|
|
||||||
val repository by lazy {
|
val repository by lazy {
|
||||||
val repository = Repository.getInstance(applicationContext)
|
val repository = Repository.getInstance(applicationContext)
|
||||||
if (repository.getRecordLogs()) {
|
if (repository.getRecordLogs()) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.heckel.ntfy.db
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.media.MediaPlayer
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
@ -18,7 +19,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
||||||
|
|
||||||
private val connectionStates = ConcurrentHashMap<Long, ConnectionState>()
|
private val connectionStates = ConcurrentHashMap<Long, ConnectionState>()
|
||||||
private val connectionStatesLiveData = MutableLiveData(connectionStates)
|
private val connectionStatesLiveData = MutableLiveData(connectionStates)
|
||||||
|
|
||||||
|
// TODO Move these into an ApplicationState singleton
|
||||||
val detailViewSubscriptionId = AtomicLong(0L) // Omg, what a hack ...
|
val detailViewSubscriptionId = AtomicLong(0L) // Omg, what a hack ...
|
||||||
|
val mediaPlayer = MediaPlayer()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d(TAG, "Created $this")
|
Log.d(TAG, "Created $this")
|
||||||
|
@ -288,6 +292,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
|
||||||
.apply()
|
.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 {
|
fun getRecordLogs(): Boolean {
|
||||||
return sharedPrefs.getBoolean(SHARED_PREFS_RECORD_LOGS_ENABLED, false) // Disabled by default
|
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_CONNECTION_PROTOCOL = "ConnectionProtocol"
|
||||||
const val SHARED_PREFS_DARK_MODE = "DarkMode"
|
const val SHARED_PREFS_DARK_MODE = "DarkMode"
|
||||||
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
|
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_RECORD_LOGS_ENABLED = "RecordLogs"
|
||||||
const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime"
|
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)
|
const val SHARED_PREFS_WEBSOCKET_REMIND_TIME = "JsonStreamRemindTime" // "Use WebSocket" banner (used to be JSON stream deprecation banner)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.MediaPlayer
|
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -24,9 +23,9 @@ import io.heckel.ntfy.ui.MainActivity
|
||||||
import io.heckel.ntfy.util.*
|
import io.heckel.ntfy.util.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class NotificationService(val context: Context) {
|
class NotificationService(val context: Context) {
|
||||||
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
private val repository = Repository.getInstance(context)
|
||||||
|
|
||||||
fun display(subscription: Subscription, notification: Notification) {
|
fun display(subscription: Subscription, notification: Notification) {
|
||||||
Log.d(TAG, "Displaying 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) {
|
private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) {
|
||||||
val title = formatTitle(subscription, notification)
|
val title = formatTitle(subscription, notification)
|
||||||
val channelId = toChannelId(notification.priority)
|
val channelId = toChannelId(notification.priority)
|
||||||
|
val insistent = notification.priority == 5 && repository.getInsistentMaxPriorityEnabled()
|
||||||
val builder = NotificationCompat.Builder(context, channelId)
|
val builder = NotificationCompat.Builder(context, channelId)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setColor(ContextCompat.getColor(context, Colors.notificationIcon(context)))
|
.setColor(ContextCompat.getColor(context, Colors.notificationIcon(context)))
|
||||||
|
@ -74,7 +74,8 @@ class NotificationService(val context: Context) {
|
||||||
.setAutoCancel(true) // Cancel when notification is clicked
|
.setAutoCancel(true) // Cancel when notification is clicked
|
||||||
setStyleAndText(builder, subscription, notification) // Preview picture or big text style
|
setStyleAndText(builder, subscription, notification) // Preview picture or big text style
|
||||||
setClickAction(builder, subscription, notification)
|
setClickAction(builder, subscription, notification)
|
||||||
maybeSetSound(builder, update)
|
maybeSetDeleteIntent(builder, insistent)
|
||||||
|
maybeSetSound(builder, insistent, update)
|
||||||
maybeSetProgress(builder, notification)
|
maybeSetProgress(builder, notification)
|
||||||
maybeAddOpenAction(builder, notification)
|
maybeAddOpenAction(builder, notification)
|
||||||
maybeAddBrowseAction(builder, notification)
|
maybeAddBrowseAction(builder, notification)
|
||||||
|
@ -82,65 +83,24 @@ class NotificationService(val context: Context) {
|
||||||
maybeAddCancelAction(builder, notification)
|
maybeAddCancelAction(builder, notification)
|
||||||
maybeAddUserActions(builder, notification)
|
maybeAddUserActions(builder, notification)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
maybeCreateNotificationChannel(notification.priority)
|
maybeCreateNotificationChannel(notification.priority)
|
||||||
val systemNotification = builder.build()
|
maybePlayInsistentSound(insistent)
|
||||||
if (channelId == CHANNEL_ID_MAX) {
|
|
||||||
//systemNotification.flags = systemNotification.flags or android.app.Notification.FLAG_INSISTENT
|
|
||||||
}
|
|
||||||
notificationManager.notify(notification.notificationId, systemNotification)
|
|
||||||
|
|
||||||
if (channelId == CHANNEL_ID_MAX) {
|
notificationManager.notify(notification.notificationId, builder.build())
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlarmReceiver : BroadcastReceiver() {
|
private fun maybeSetDeleteIntent(builder: NotificationCompat.Builder, insistent: Boolean) {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
if (!insistent) {
|
||||||
Log.d(TAG, "AlarmReceiver.onReceive ${intent}")
|
return
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
private fun maybeSetSound(builder: NotificationCompat.Builder, insistent: Boolean, update: Boolean) {
|
||||||
if (!update) {
|
val hasSound = !update && !insistent
|
||||||
|
if (hasSound) {
|
||||||
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
||||||
builder.setSound(defaultSoundUri)
|
builder.setSound(defaultSoundUri)
|
||||||
} else {
|
} 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? {
|
private fun detailActivityIntent(subscription: Subscription): PendingIntent? {
|
||||||
val intent = Intent(context, DetailActivity::class.java).apply {
|
val intent = Intent(context, DetailActivity::class.java).apply {
|
||||||
putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
|
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.
|
* Activity used to launch a URL.
|
||||||
* .
|
* .
|
||||||
|
|
|
@ -297,6 +297,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
repository.mediaPlayer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|
|
@ -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<SwitchPreference> { 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
|
// Channel settings
|
||||||
val channelPrefsPrefId = context?.getString(R.string.settings_notifications_channel_prefs_key) ?: return
|
val channelPrefsPrefId = context?.getString(R.string.settings_notifications_channel_prefs_key) ?: return
|
||||||
val channelPrefs: Preference? = findPreference(channelPrefsPrefId)
|
val channelPrefs: Preference? = findPreference(channelPrefsPrefId)
|
||||||
|
|
|
@ -279,6 +279,9 @@
|
||||||
<string name="settings_notifications_auto_delete_one_week">After one week</string>
|
<string name="settings_notifications_auto_delete_one_week">After one week</string>
|
||||||
<string name="settings_notifications_auto_delete_one_month">After one month</string>
|
<string name="settings_notifications_auto_delete_one_month">After one month</string>
|
||||||
<string name="settings_notifications_auto_delete_three_months">After 3 months</string>
|
<string name="settings_notifications_auto_delete_three_months">After 3 months</string>
|
||||||
|
<string name="settings_notifications_insistent_max_priority_title">Keep alerting for highest priority</string>
|
||||||
|
<string name="settings_notifications_insistent_max_priority_summary_enabled">Max priority notifications continuously play the notification sound until dismissed. This overrides Do Not Disturb mode.</string>
|
||||||
|
<string name="settings_notifications_insistent_max_priority_summary_disabled">Max priority notifications alert only once. If enabled, the notification sound will repeat and override Do Not Disturb mode.</string>
|
||||||
<string name="settings_general_header">General</string>
|
<string name="settings_general_header">General</string>
|
||||||
<string name="settings_general_default_base_url_title">Default server</string>
|
<string name="settings_general_default_base_url_title">Default server</string>
|
||||||
<string name="settings_general_default_base_url_message">Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics.</string>
|
<string name="settings_general_default_base_url_message">Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics.</string>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<string name="settings_notifications_channel_prefs_key" translatable="false">ChannelPrefs</string>
|
<string name="settings_notifications_channel_prefs_key" translatable="false">ChannelPrefs</string>
|
||||||
<string name="settings_notifications_auto_download_key" translatable="false">AutoDownload</string>
|
<string name="settings_notifications_auto_download_key" translatable="false">AutoDownload</string>
|
||||||
<string name="settings_notifications_auto_delete_key" translatable="false">AutoDelete</string>
|
<string name="settings_notifications_auto_delete_key" translatable="false">AutoDelete</string>
|
||||||
|
<string name="settings_notifications_insistent_max_priority_key" translatable="false">InsistentMaxPriority</string>
|
||||||
<string name="settings_general_default_base_url_key" translatable="false">DefaultBaseURL</string>
|
<string name="settings_general_default_base_url_key" translatable="false">DefaultBaseURL</string>
|
||||||
<string name="settings_general_users_key" translatable="false">ManageUsers</string>
|
<string name="settings_general_users_key" translatable="false">ManageUsers</string>
|
||||||
<string name="settings_general_dark_mode_key" translatable="false">DarkMode</string>
|
<string name="settings_general_dark_mode_key" translatable="false">DarkMode</string>
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
app:entries="@array/settings_notifications_auto_delete_entries"
|
app:entries="@array/settings_notifications_auto_delete_entries"
|
||||||
app:entryValues="@array/settings_notifications_auto_delete_values"
|
app:entryValues="@array/settings_notifications_auto_delete_values"
|
||||||
app:defaultValue="2592000"/>
|
app:defaultValue="2592000"/>
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="@string/settings_notifications_insistent_max_priority_key"
|
||||||
|
app:title="@string/settings_notifications_insistent_max_priority_title"
|
||||||
|
app:defaultValue="false"/>
|
||||||
<Preference
|
<Preference
|
||||||
app:key="@string/settings_notifications_channel_prefs_key"
|
app:key="@string/settings_notifications_channel_prefs_key"
|
||||||
app:title="@string/settings_notifications_channel_prefs_title"
|
app:title="@string/settings_notifications_channel_prefs_title"
|
||||||
|
|
Loading…
Reference in a new issue