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">
|
||||
</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) -->
|
||||
<service
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
package io.heckel.ntfy.app
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import io.heckel.ntfy.db.Database
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.util.Log
|
||||
|
||||
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 = Repository.getInstance(applicationContext)
|
||||
if (repository.getRecordLogs()) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.heckel.ntfy.db
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import androidx.annotation.WorkerThread
|
||||
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 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)
|
||||
|
|
|
@ -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.
|
||||
* .
|
||||
|
|
|
@ -297,6 +297,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
|
|||
}
|
||||
}
|
||||
}
|
||||
repository.mediaPlayer.stop()
|
||||
}
|
||||
|
||||
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
|
||||
val channelPrefsPrefId = context?.getString(R.string.settings_notifications_channel_prefs_key) ?: return
|
||||
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_month">After one month</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_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>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<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_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_users_key" translatable="false">ManageUsers</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:entryValues="@array/settings_notifications_auto_delete_values"
|
||||
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
|
||||
app:key="@string/settings_notifications_channel_prefs_key"
|
||||
app:title="@string/settings_notifications_channel_prefs_title"
|
||||
|
|
Loading…
Reference in a new issue