2021-11-12 01:41:29 +01:00
package io.heckel.ntfy.msg
2022-04-24 02:46:41 +02:00
import android.app.*
2022-05-04 01:59:33 +02:00
import android.content.ActivityNotFoundException
2022-04-22 01:47:18 +02:00
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
2022-11-28 12:00:41 +01:00
import android.media.AudioAttributes
import android.media.AudioManager
2021-11-12 01:41:29 +01:00
import android.media.RingtoneManager
2022-01-04 19:45:02 +01:00
import android.net.Uri
2021-11-12 01:41:29 +01:00
import android.os.Build
2022-04-24 02:46:41 +02:00
import android.os.Bundle
2022-05-04 01:59:33 +02:00
import android.widget.Toast
2021-11-12 01:41:29 +01:00
import androidx.core.app.NotificationCompat
2021-11-23 16:52:27 +01:00
import androidx.core.content.ContextCompat
2021-11-12 01:41:29 +01:00
import io.heckel.ntfy.R
2022-01-18 20:28:48 +01:00
import io.heckel.ntfy.db.*
2022-04-24 02:46:41 +02:00
import io.heckel.ntfy.db.Notification
2022-02-08 00:35:36 +01:00
import io.heckel.ntfy.ui.Colors
2021-11-12 01:41:29 +01:00
import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity
2022-01-09 04:17:41 +01:00
import io.heckel.ntfy.util.*
2022-04-17 20:29:29 +02:00
import java.util.*
2021-11-12 01:41:29 +01:00
class NotificationService ( val context : Context ) {
2022-01-06 01:05:57 +01:00
private val notificationManager = context . getSystemService ( Context . NOTIFICATION _SERVICE ) as NotificationManager
2022-11-30 04:46:38 +01:00
private val repository = Repository . getInstance ( context )
2022-01-06 01:05:57 +01:00
2022-01-04 00:54:18 +01:00
fun display ( subscription : Subscription , notification : Notification ) {
2021-11-27 22:18:09 +01:00
Log . d ( TAG , " Displaying notification $notification " )
2022-01-04 19:45:02 +01:00
displayInternal ( subscription , notification )
2022-01-04 00:54:18 +01:00
}
2022-01-10 04:08:29 +01:00
fun update ( subscription : Subscription , notification : Notification ) {
val active = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . M ) {
notificationManager . activeNotifications . find { it . id == notification . notificationId } != null
} else {
true
}
if ( active ) {
Log . d ( TAG , " Updating notification $notification " )
displayInternal ( subscription , notification , update = true )
}
2022-01-05 21:40:40 +01:00
}
2022-01-04 00:54:18 +01:00
fun cancel ( notification : Notification ) {
if ( notification . notificationId != 0 ) {
2022-03-13 20:58:19 +01:00
Log . d ( TAG , " Cancelling notification ${notification.id} : ${decodeMessage(notification)} " )
2022-01-04 00:54:18 +01:00
notificationManager . cancel ( notification . notificationId )
}
}
2022-04-24 02:46:41 +02:00
fun cancel ( notificationId : Int ) {
if ( notificationId != 0 ) {
Log . d ( TAG , " Cancelling notification ${notificationId} " )
notificationManager . cancel ( notificationId )
}
}
2022-01-04 00:54:18 +01:00
fun createNotificationChannels ( ) {
2022-12-06 21:48:04 +01:00
ALL_PRIORITIES . forEach { priority -> maybeCreateNotificationChannel ( priority ) }
2022-01-04 00:54:18 +01:00
}
2022-01-10 04:08:29 +01:00
private fun displayInternal ( subscription : Subscription , notification : Notification , update : Boolean = false ) {
2021-11-27 22:18:09 +01:00
val title = formatTitle ( subscription , notification )
val channelId = toChannelId ( notification . priority )
2022-12-06 21:48:04 +01:00
val insistent = notification . priority == PRIORITY _MAX &&
2022-12-06 21:29:09 +01:00
( repository . getInsistentMaxPriorityEnabled ( ) || subscription . insistent == Repository . INSISTENT _MAX _PRIORITY _ENABLED )
2022-01-06 01:05:57 +01:00
val builder = NotificationCompat . Builder ( context , channelId )
2021-11-23 16:52:27 +01:00
. setSmallIcon ( R . drawable . ic _notification )
2022-11-26 19:52:52 +01:00
. setColor ( ContextCompat . getColor ( context , Colors . notificationIcon ( context ) ) )
2021-11-12 01:41:29 +01:00
. setContentTitle ( title )
2022-01-06 01:05:57 +01:00
. setOnlyAlertOnce ( true ) // Do not vibrate or play sound if already showing (updates!)
2021-11-12 01:41:29 +01:00
. setAutoCancel ( true ) // Cancel when notification is clicked
2022-05-09 04:57:52 +02:00
setStyleAndText ( builder , subscription , notification ) // Preview picture or big text style
2022-01-11 23:00:18 +01:00
setClickAction ( builder , subscription , notification )
2022-11-30 04:46:38 +01:00
maybeSetDeleteIntent ( builder , insistent )
maybeSetSound ( builder , insistent , update )
2022-01-10 04:08:29 +01:00
maybeSetProgress ( builder , notification )
2022-01-06 01:05:57 +01:00
maybeAddOpenAction ( builder , notification )
2022-01-09 03:32:43 +01:00
maybeAddBrowseAction ( builder , notification )
2022-01-11 23:00:18 +01:00
maybeAddDownloadAction ( builder , notification )
2022-01-12 00:21:30 +01:00
maybeAddCancelAction ( builder , notification )
2022-04-19 15:15:06 +02:00
maybeAddUserActions ( builder , notification )
2022-01-06 01:05:57 +01:00
maybeCreateNotificationChannel ( notification . priority )
2022-11-30 04:46:38 +01:00
maybePlayInsistentSound ( insistent )
2022-11-28 12:00:41 +01:00
2022-11-30 04:46:38 +01:00
notificationManager . notify ( notification . notificationId , builder . build ( ) )
2022-11-28 12:00:41 +01:00
}
2022-11-30 04:46:38 +01:00
private fun maybeSetDeleteIntent ( builder : NotificationCompat . Builder , insistent : Boolean ) {
if ( !in sistent ) {
return
2022-11-28 12:00:41 +01:00
}
2022-11-30 04:46:38 +01:00
val intent = Intent ( context , DeleteBroadcastReceiver :: class . java )
val pendingIntent = PendingIntent . getBroadcast ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _IMMUTABLE )
builder . setDeleteIntent ( pendingIntent )
2022-01-06 01:05:57 +01:00
}
2021-11-12 01:41:29 +01:00
2022-11-30 04:46:38 +01:00
private fun maybeSetSound ( builder : NotificationCompat . Builder , insistent : Boolean , update : Boolean ) {
2022-12-05 02:28:57 +01:00
// Note that the sound setting is ignored in Android => O (26) in favor of notification channels
2022-11-30 04:46:38 +01:00
val hasSound = ! update && !in sistent
if ( hasSound ) {
2022-01-06 01:05:57 +01:00
val defaultSoundUri = RingtoneManager . getDefaultUri ( RingtoneManager . TYPE _NOTIFICATION )
builder . setSound ( defaultSoundUri )
} else {
builder . setSound ( null )
}
}
2022-05-09 04:57:52 +02:00
private fun setStyleAndText ( builder : NotificationCompat . Builder , subscription : Subscription , notification : Notification ) {
2022-01-09 03:32:43 +01:00
val contentUri = notification . attachment ?. contentUri
2022-01-09 04:17:41 +01:00
val isSupportedImage = supportedImage ( notification . attachment ?. type )
2022-05-09 04:57:52 +02:00
val subscriptionIcon = if ( subscription . icon != null ) subscription . icon . readBitmapFromUriOrNull ( context ) else null
2022-08-26 05:58:37 +02:00
val notificationIcon = if ( notification . icon != null ) notification . icon . contentUri ?. readBitmapFromUriOrNull ( context ) else null
2022-07-16 22:32:09 +02:00
val largeIcon = notificationIcon ?: subscriptionIcon
2022-01-09 04:17:41 +01:00
if ( contentUri != null && isSupportedImage ) {
2022-01-06 01:05:57 +01:00
try {
2022-05-09 04:57:52 +02:00
val attachmentBitmap = contentUri . readBitmapFromUri ( context )
2022-01-06 01:05:57 +01:00
builder
2022-04-19 15:15:06 +02:00
. setContentText ( maybeAppendActionErrors ( formatMessage ( notification ) , notification ) )
2022-05-09 04:57:52 +02:00
. setLargeIcon ( attachmentBitmap )
2022-01-06 01:05:57 +01:00
. setStyle ( NotificationCompat . BigPictureStyle ( )
2022-05-09 04:57:52 +02:00
. bigPicture ( attachmentBitmap )
2022-07-16 22:32:09 +02:00
. bigLargeIcon ( largeIcon ) ) // May be null
2022-01-06 01:05:57 +01:00
} catch ( _ : Exception ) {
2022-04-19 15:15:06 +02:00
val message = maybeAppendActionErrors ( formatMessageMaybeWithAttachmentInfos ( notification ) , notification )
2022-01-11 23:00:18 +01:00
builder
. setContentText ( message )
. setStyle ( NotificationCompat . BigTextStyle ( ) . bigText ( message ) )
2022-01-06 01:05:57 +01:00
}
} else {
2022-04-19 15:15:06 +02:00
val message = maybeAppendActionErrors ( formatMessageMaybeWithAttachmentInfos ( notification ) , notification )
2022-01-11 23:00:18 +01:00
builder
. setContentText ( message )
. setStyle ( NotificationCompat . BigTextStyle ( ) . bigText ( message ) )
2022-07-16 22:32:09 +02:00
. setLargeIcon ( largeIcon ) // May be null
2022-01-11 23:00:18 +01:00
}
}
2022-04-19 15:15:06 +02:00
private fun formatMessageMaybeWithAttachmentInfos ( notification : Notification ) : String {
2022-01-11 23:00:18 +01:00
val message = formatMessage ( notification )
val attachment = notification . attachment ?: return message
2022-04-19 15:15:06 +02:00
val attachmentInfos = if ( attachment . size != null ) {
2022-01-11 23:00:18 +01:00
" ${attachment.name} , ${formatBytes(attachment.size)} "
} else {
attachment . name
}
if ( attachment . progress in 0. . 99 ) {
2022-04-19 15:15:06 +02:00
return context . getString ( R . string . notification _popup _file _downloading , attachmentInfos , attachment . progress , message )
2022-01-06 01:05:57 +01:00
}
2022-04-19 15:15:06 +02:00
if ( attachment . progress == ATTACHMENT _PROGRESS _DONE ) {
return context . getString ( R . string . notification _popup _file _download _successful , message , attachmentInfos )
2022-01-11 23:00:18 +01:00
}
2022-04-19 15:15:06 +02:00
if ( attachment . progress == ATTACHMENT _PROGRESS _FAILED ) {
return context . getString ( R . string . notification _popup _file _download _failed , message , attachmentInfos )
}
return context . getString ( R . string . notification _popup _file , message , attachmentInfos )
}
2022-01-11 23:00:18 +01:00
private fun setClickAction ( builder : NotificationCompat . Builder , subscription : Subscription , notification : Notification ) {
2022-01-06 01:05:57 +01:00
if ( notification . click == " " ) {
builder . setContentIntent ( detailActivityIntent ( subscription ) )
} else {
try {
val uri = Uri . parse ( notification . click )
2022-04-19 15:15:06 +02:00
val viewIntent = PendingIntent . getActivity ( context , Random ( ) . nextInt ( ) , Intent ( Intent . ACTION _VIEW , uri ) , PendingIntent . FLAG _IMMUTABLE )
2022-01-06 01:05:57 +01:00
builder . setContentIntent ( viewIntent )
} catch ( e : Exception ) {
builder . setContentIntent ( detailActivityIntent ( subscription ) )
}
2022-01-04 19:45:02 +01:00
}
2022-01-06 01:05:57 +01:00
}
2022-01-10 04:08:29 +01:00
private fun maybeSetProgress ( builder : NotificationCompat . Builder , notification : Notification ) {
val progress = notification . attachment ?. progress
2022-01-09 03:32:43 +01:00
if ( progress in 0. . 99 ) {
2022-01-10 04:08:29 +01:00
builder . setProgress ( 100 , progress !! , false )
2022-01-04 00:54:18 +01:00
} else {
2022-01-06 01:05:57 +01:00
builder . setProgress ( 0 , 0 , false ) // Remove progress bar
}
}
2022-01-09 03:32:43 +01:00
private fun maybeAddOpenAction ( builder : NotificationCompat . Builder , notification : Notification ) {
2022-12-06 02:24:05 +01:00
if ( ! canOpenAttachment ( notification . attachment ) ) {
return
}
2022-01-08 21:49:07 +01:00
if ( notification . attachment ?. contentUri != null ) {
val contentUri = Uri . parse ( notification . attachment . contentUri )
2022-04-17 04:32:29 +02:00
val intent = Intent ( Intent . ACTION _VIEW , contentUri ) . apply {
setDataAndType ( contentUri , notification . attachment . type ?: " application/octet-stream " ) // Required for Android <= P
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION )
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getActivity ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _IMMUTABLE )
2022-01-09 03:32:43 +01:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , context . getString ( R . string . notification _popup _action _open ) , pendingIntent ) . build ( ) )
2022-01-04 00:54:18 +01:00
}
2022-01-06 01:05:57 +01:00
}
2021-11-12 01:41:29 +01:00
2022-01-09 03:32:43 +01:00
private fun maybeAddBrowseAction ( builder : NotificationCompat . Builder , notification : Notification ) {
if ( notification . attachment ?. contentUri != null ) {
2022-04-17 04:32:29 +02:00
val intent = Intent ( android . app . DownloadManager . ACTION _VIEW _DOWNLOADS ) . apply {
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION )
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getActivity ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _IMMUTABLE )
2022-01-09 03:32:43 +01:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , context . getString ( R . string . notification _popup _action _browse ) , pendingIntent ) . build ( ) )
2022-01-06 01:05:57 +01:00
}
2021-11-15 22:24:31 +01:00
}
2022-01-11 23:00:18 +01:00
private fun maybeAddDownloadAction ( builder : NotificationCompat . Builder , notification : Notification ) {
2022-04-19 15:15:06 +02:00
if ( notification . attachment ?. contentUri == null && listOf ( ATTACHMENT _PROGRESS _NONE , ATTACHMENT _PROGRESS _FAILED ) . contains ( notification . attachment ?. progress ) ) {
2022-04-17 04:32:29 +02:00
val intent = Intent ( context , UserActionBroadcastReceiver :: class . java ) . apply {
putExtra ( BROADCAST _EXTRA _TYPE , BROADCAST _TYPE _DOWNLOAD _START )
putExtra ( BROADCAST _EXTRA _NOTIFICATION _ID , notification . id )
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getBroadcast ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
2022-01-11 23:00:18 +01:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , context . getString ( R . string . notification _popup _action _download ) , pendingIntent ) . build ( ) )
}
}
2022-01-12 00:21:30 +01:00
private fun maybeAddCancelAction ( builder : NotificationCompat . Builder , notification : Notification ) {
if ( notification . attachment ?. contentUri == null && notification . attachment ?. progress in 0. . 99 ) {
2022-04-17 04:32:29 +02:00
val intent = Intent ( context , UserActionBroadcastReceiver :: class . java ) . apply {
putExtra ( BROADCAST _EXTRA _TYPE , BROADCAST _TYPE _DOWNLOAD _CANCEL )
putExtra ( BROADCAST _EXTRA _NOTIFICATION _ID , notification . id )
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getBroadcast ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
2022-01-12 00:21:30 +01:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , context . getString ( R . string . notification _popup _action _cancel ) , pendingIntent ) . build ( ) )
}
}
2022-04-19 15:15:06 +02:00
private fun maybeAddUserActions ( builder : NotificationCompat . Builder , notification : Notification ) {
2022-04-17 02:12:40 +02:00
notification . actions ?. forEach { action ->
2022-04-23 04:45:33 +02:00
val actionType = action . action . lowercase ( Locale . getDefault ( ) )
2022-04-24 02:46:41 +02:00
if ( actionType == ACTION _VIEW ) {
// Hack: Action "view" with "clear=true" is a special case, because it's apparently impossible to start a
// URL activity from PendingIntent.getActivity() and also close the notification. To clear it, we
// launch our own Activity (ViewActionWithClearActivity) which then calls the actual activity
if ( action . clear == true ) {
addViewUserActionWithClear ( builder , notification , action )
} else {
addViewUserActionWithoutClear ( builder , action )
}
2022-04-23 04:45:33 +02:00
} else {
addHttpOrBroadcastUserAction ( builder , notification , action )
2022-04-17 02:12:40 +02:00
}
}
}
2022-04-24 02:46:41 +02:00
/ * *
* Open the URL and do NOT cancel the notification ( clear = false ) . This uses a normal Intent with the given URL .
* The other case is much more interesting .
* /
2022-04-23 04:45:33 +02:00
private fun addViewUserActionWithoutClear ( builder : NotificationCompat . Builder , action : Action ) {
2022-04-17 02:12:40 +02:00
try {
2022-04-17 04:32:29 +02:00
val url = action . url ?: return
val intent = Intent ( Intent . ACTION _VIEW , Uri . parse ( url ) ) . apply {
2022-04-24 02:46:41 +02:00
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION or Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK )
}
val pendingIntent = PendingIntent . getActivity ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _IMMUTABLE )
builder . addAction ( NotificationCompat . Action . Builder ( 0 , action . label , pendingIntent ) . build ( ) )
} catch ( e : Exception ) {
Log . w ( TAG , " Unable to add open user action " , e )
}
}
/ * *
* HACK : Open the URL and CANCEL the notification ( clear = true ) . This is a SPECIAL case with a horrible workaround .
* We call our own activity ViewActionWithClearActivity and open the URL from there .
* /
private fun addViewUserActionWithClear ( builder : NotificationCompat . Builder , notification : Notification , action : Action ) {
try {
val url = action . url ?: return
val intent = Intent ( context , ViewActionWithClearActivity :: class . java ) . apply {
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION or Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK )
putExtra ( VIEW _ACTION _EXTRA _URL , url )
putExtra ( VIEW _ACTION _EXTRA _NOTIFICATION _ID , notification . notificationId )
2022-04-17 04:32:29 +02:00
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getActivity ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _IMMUTABLE )
2022-04-17 02:12:40 +02:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , action . label , pendingIntent ) . build ( ) )
} catch ( e : Exception ) {
Log . w ( TAG , " Unable to add open user action " , e )
}
}
2022-04-23 04:45:33 +02:00
private fun addHttpOrBroadcastUserAction ( builder : NotificationCompat . Builder , notification : Notification , action : Action ) {
2022-04-17 04:32:29 +02:00
val intent = Intent ( context , UserActionBroadcastReceiver :: class . java ) . apply {
2022-04-17 20:29:29 +02:00
putExtra ( BROADCAST _EXTRA _TYPE , BROADCAST _TYPE _USER _ACTION )
2022-04-17 04:32:29 +02:00
putExtra ( BROADCAST _EXTRA _NOTIFICATION _ID , notification . id )
2022-04-17 20:29:29 +02:00
putExtra ( BROADCAST _EXTRA _ACTION _ID , action . id )
2022-04-17 04:32:29 +02:00
}
2022-04-19 15:15:06 +02:00
val pendingIntent = PendingIntent . getBroadcast ( context , Random ( ) . nextInt ( ) , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
2022-04-20 01:20:39 +02:00
val label = formatActionLabel ( action )
2022-04-19 15:15:06 +02:00
builder . addAction ( NotificationCompat . Action . Builder ( 0 , label , pendingIntent ) . build ( ) )
2022-04-17 04:32:29 +02:00
}
2022-04-24 02:46:41 +02:00
/ * *
* Receives the broadcast from
2022-07-16 22:32:09 +02:00
* - the " http " and " broadcast " action button ( the " view " action is handled differently )
2022-04-24 02:46:41 +02:00
* - the " download " / " cancel " action button
*
* Then queues a Worker via WorkManager to execute the action in the background
* /
2022-04-17 04:32:29 +02:00
class UserActionBroadcastReceiver : BroadcastReceiver ( ) {
2022-01-11 23:00:18 +01:00
override fun onReceive ( context : Context , intent : Intent ) {
2022-04-17 04:32:29 +02:00
val type = intent . getStringExtra ( BROADCAST _EXTRA _TYPE ) ?: return
val notificationId = intent . getStringExtra ( BROADCAST _EXTRA _NOTIFICATION _ID ) ?: return
when ( type ) {
2022-07-16 22:32:09 +02:00
BROADCAST _TYPE _DOWNLOAD _START -> DownloadManager . enqueue ( context , notificationId , userAction = true , DownloadType . ATTACHMENT )
2022-04-17 04:32:29 +02:00
BROADCAST _TYPE _DOWNLOAD _CANCEL -> DownloadManager . cancel ( context , notificationId )
2022-04-17 20:29:29 +02:00
BROADCAST _TYPE _USER _ACTION -> {
val actionId = intent . getStringExtra ( BROADCAST _EXTRA _ACTION _ID ) ?: return
UserActionManager . enqueue ( context , notificationId , actionId )
}
2022-01-12 00:21:30 +01:00
}
2022-01-11 23:00:18 +01:00
}
}
2022-11-30 04:46:38 +01:00
/ * *
* 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 ) {
2022-12-05 02:11:46 +01:00
Log . d ( TAG , " Media player: Stopping insistent ring " )
2022-11-30 04:46:38 +01:00
val mediaPlayer = Repository . getInstance ( context ) . mediaPlayer
mediaPlayer . stop ( )
}
}
2022-01-04 23:45:24 +01:00
private fun detailActivityIntent ( subscription : Subscription ) : PendingIntent ? {
2022-04-24 02:46:41 +02:00
val intent = Intent ( context , DetailActivity :: class . java ) . apply {
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _ID , subscription . id )
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _BASE _URL , subscription . baseUrl )
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _TOPIC , subscription . topic )
2022-06-24 17:15:22 +02:00
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _DISPLAY _NAME , displayName ( subscription ) )
2022-04-24 02:46:41 +02:00
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _INSTANT , subscription . instant )
putExtra ( MainActivity . EXTRA _SUBSCRIPTION _MUTED _UNTIL , subscription . mutedUntil )
}
2022-01-04 23:45:24 +01:00
return TaskStackBuilder . create ( context ) . run {
addNextIntentWithParentStack ( intent ) // Add the intent, which inflates the back stack
2022-11-28 12:42:37 +01:00
getPendingIntent ( Random ( ) . nextInt ( ) , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE ) // Get the PendingIntent containing the entire back stack
2022-01-04 23:45:24 +01:00
}
}
2022-01-06 01:05:57 +01:00
private fun maybeCreateNotificationChannel ( priority : Int ) {
2021-11-29 20:06:08 +01:00
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . O ) {
// Note: To change a notification channel, you must delete the old one and create a new one!
val pause = 300L
val channel = when ( priority ) {
2022-12-06 21:48:04 +01:00
PRIORITY _MIN -> NotificationChannel ( CHANNEL _ID _MIN , context . getString ( R . string . channel _notifications _min _name ) , NotificationManager . IMPORTANCE _MIN )
PRIORITY _LOW -> NotificationChannel ( CHANNEL _ID _LOW , context . getString ( R . string . channel _notifications _low _name ) , NotificationManager . IMPORTANCE _LOW )
PRIORITY _HIGH -> {
2021-11-29 20:06:08 +01:00
val channel = NotificationChannel ( CHANNEL _ID _HIGH , context . getString ( R . string . channel _notifications _high _name ) , NotificationManager . IMPORTANCE _HIGH )
channel . enableVibration ( true )
channel . vibrationPattern = longArrayOf (
pause , 100 , pause , 100 , pause , 100 ,
pause , 2000
)
channel
}
2022-12-06 21:48:04 +01:00
PRIORITY _MAX -> {
2022-01-14 18:32:36 +01:00
val channel = NotificationChannel ( CHANNEL _ID _MAX , context . getString ( R . string . channel _notifications _max _name ) , NotificationManager . IMPORTANCE _HIGH ) // IMPORTANCE_MAX does not exist
2021-11-29 20:06:08 +01:00
channel . enableLights ( true )
channel . enableVibration ( true )
2022-11-28 12:00:41 +01:00
channel . setBypassDnd ( true )
2021-11-29 20:06:08 +01:00
channel . vibrationPattern = longArrayOf (
pause , 100 , pause , 100 , pause , 100 ,
pause , 2000 ,
pause , 100 , pause , 100 , pause , 100 ,
pause , 2000 ,
pause , 100 , pause , 100 , pause , 100 ,
pause , 2000
)
channel
}
else -> NotificationChannel ( CHANNEL _ID _DEFAULT , context . getString ( R . string . channel _notifications _default _name ) , NotificationManager . IMPORTANCE _DEFAULT )
}
notificationManager . createNotificationChannel ( channel )
2021-11-27 22:18:09 +01:00
}
}
private fun toChannelId ( priority : Int ) : String {
return when ( priority ) {
2022-12-06 21:48:04 +01:00
PRIORITY _MIN -> CHANNEL _ID _MIN
PRIORITY _LOW -> CHANNEL _ID _LOW
PRIORITY _HIGH -> CHANNEL _ID _HIGH
PRIORITY _MAX -> CHANNEL _ID _MAX
2021-11-27 22:18:09 +01:00
else -> CHANNEL _ID _DEFAULT
}
}
2022-11-30 04:46:38 +01:00
private fun maybePlayInsistentSound ( insistent : Boolean ) {
if ( !in sistent ) {
return
}
try {
val mediaPlayer = repository . mediaPlayer
val audioManager = context . getSystemService ( Context . AUDIO _SERVICE ) as AudioManager
if ( audioManager . getStreamVolume ( AudioManager . STREAM _ALARM ) != 0 ) {
2022-12-05 02:11:46 +01:00
Log . d ( TAG , " Media player: Playing insistent alarm on alarm channel " )
2022-11-30 04:46:38 +01:00
mediaPlayer . reset ( )
2022-12-06 21:48:04 +01:00
mediaPlayer . setDataSource ( context , getInsistentSound ( ) )
2022-11-30 04:46:38 +01:00
mediaPlayer . setAudioAttributes ( AudioAttributes . Builder ( ) . setUsage ( AudioAttributes . USAGE _ALARM ) . build ( ) )
mediaPlayer . isLooping = true ;
mediaPlayer . prepare ( )
mediaPlayer . start ( )
2022-12-05 02:11:46 +01:00
} else {
Log . d ( TAG , " Media player: Alarm volume is 0; not playing insistent alarm " )
2022-11-30 04:46:38 +01:00
}
} catch ( e : Exception ) {
2022-12-05 02:11:46 +01:00
Log . w ( TAG , " Media player: Failed to play insistent alarm " , e )
2022-11-30 04:46:38 +01:00
}
}
2022-12-06 21:48:04 +01:00
private fun getInsistentSound ( ) : Uri {
return if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . O ) {
val channel = notificationManager . getNotificationChannel ( toChannelId ( PRIORITY _MAX ) )
channel . sound
} else {
RingtoneManager . getDefaultUri ( RingtoneManager . TYPE _NOTIFICATION )
}
}
2022-04-24 02:46:41 +02:00
/ * *
* Activity used to launch a URL .
* .
* Horrible hack : Action " view " with " clear=true " is a special case , because it ' s apparently impossible to start a
* URL activity from PendingIntent . getActivity ( ) and also close the notification . To clear it , we
* launch this activity which then calls the actual activity .
* /
class ViewActionWithClearActivity : Activity ( ) {
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate ( savedInstanceState )
2022-05-04 01:59:33 +02:00
Log . d ( TAG , " Created $this " )
2022-04-24 02:46:41 +02:00
val url = intent . getStringExtra ( VIEW _ACTION _EXTRA _URL )
val notificationId = intent . getIntExtra ( VIEW _ACTION _EXTRA _NOTIFICATION _ID , 0 )
if ( url == null ) {
finish ( )
return
}
// Immediately start the actual activity
try {
val intent = Intent ( Intent . ACTION _VIEW , Uri . parse ( url ) ) . apply {
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION )
}
startActivity ( intent )
} catch ( e : Exception ) {
Log . w ( TAG , " Unable to start activity from URL $url " , e )
2022-05-04 01:59:33 +02:00
val message = if ( e is ActivityNotFoundException ) url else e . message
Toast
. makeText ( this , getString ( R . string . detail _item _cannot _open _url , message ) , Toast . LENGTH _LONG )
. show ( )
2022-04-24 02:46:41 +02:00
}
// Cancel notification
val notifier = NotificationService ( this )
notifier . cancel ( notificationId )
// Close this activity
finish ( )
}
}
2021-11-12 01:41:29 +01:00
companion object {
2022-04-20 01:20:39 +02:00
const val ACTION _VIEW = " view "
const val ACTION _HTTP = " http "
const val ACTION _BROADCAST = " broadcast "
2022-04-19 15:15:06 +02:00
2022-04-20 01:20:39 +02:00
const val BROADCAST _EXTRA _TYPE = " type "
const val BROADCAST _EXTRA _NOTIFICATION _ID = " notificationId "
2022-04-23 04:45:33 +02:00
const val BROADCAST _EXTRA _ACTION _ID = " actionId "
2022-04-17 04:32:29 +02:00
2022-04-20 01:20:39 +02:00
const val BROADCAST _TYPE _DOWNLOAD _START = " io.heckel.ntfy.DOWNLOAD_ACTION_START "
const val BROADCAST _TYPE _DOWNLOAD _CANCEL = " io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL "
const val BROADCAST _TYPE _USER _ACTION = " io.heckel.ntfy.USER_ACTION_RUN "
2022-04-17 04:32:29 +02:00
2022-04-20 01:20:39 +02:00
private const val TAG = " NtfyNotifService "
2022-01-09 03:32:43 +01:00
2021-11-27 22:18:09 +01:00
private const val CHANNEL _ID _MIN = " ntfy-min "
private const val CHANNEL _ID _LOW = " ntfy-low "
private const val CHANNEL _ID _DEFAULT = " ntfy "
private const val CHANNEL _ID _HIGH = " ntfy-high "
private const val CHANNEL _ID _MAX = " ntfy-max "
2022-04-24 02:46:41 +02:00
private const val VIEW _ACTION _EXTRA _URL = " url "
private const val VIEW _ACTION _EXTRA _NOTIFICATION _ID = " notificationId "
2021-11-12 01:41:29 +01:00
}
}