ntfy-android/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt

223 lines
10 KiB
Kotlin
Raw Normal View History

package io.heckel.ntfy.msg
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
2022-01-04 12:54:18 +13:00
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.RingtoneManager
2022-01-05 07:45:02 +13:00
import android.net.Uri
import android.os.Build
2022-01-05 07:45:02 +13:00
import android.os.Environment
import android.util.Log
import androidx.core.app.NotificationCompat
2021-11-24 04:52:27 +13:00
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Notification
2021-11-14 13:26:37 +13:00
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity
2021-11-28 10:18:09 +13:00
import io.heckel.ntfy.util.formatMessage
import io.heckel.ntfy.util.formatTitle
2022-01-04 12:54:18 +13:00
import okhttp3.OkHttpClient
import okhttp3.Request
2022-01-05 07:45:02 +13:00
import java.io.File
2022-01-04 12:54:18 +13:00
import java.util.concurrent.TimeUnit
class NotificationService(val context: Context) {
2022-01-04 12:54:18 +13:00
private val client = OkHttpClient.Builder()
.callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
fun display(subscription: Subscription, notification: Notification) {
2021-11-28 10:18:09 +13:00
Log.d(TAG, "Displaying notification $notification")
2022-01-05 07:45:02 +13:00
displayInternal(subscription, notification)
if (notification.attachmentPreviewUrl != null) {
downloadPreviewAndUpdate(subscription, notification)
2022-01-04 12:54:18 +13:00
}
}
fun cancel(notification: Notification) {
if (notification.notificationId != 0) {
Log.d(TAG, "Cancelling notification ${notification.id}: ${notification.message}")
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(notification.notificationId)
}
}
fun createNotificationChannels() {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
(1..5).forEach { priority -> maybeCreateNotificationChannel(notificationManager, priority) }
}
private fun displayInternal(subscription: Subscription, notification: Notification, bitmap: Bitmap? = null) {
2021-11-28 10:18:09 +13:00
val title = formatTitle(subscription, notification)
val message = formatMessage(notification)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
2021-11-28 10:18:09 +13:00
val channelId = toChannelId(notification.priority)
2022-01-04 12:54:18 +13:00
var notificationBuilder = NotificationCompat.Builder(context, channelId)
2021-11-24 04:52:27 +13:00
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.primaryColor))
.setContentTitle(title)
2021-11-28 10:18:09 +13:00
.setContentText(message)
.setSound(defaultSoundUri)
.setAutoCancel(true) // Cancel when notification is clicked
2022-01-05 11:45:24 +13:00
notificationBuilder = setContentIntent(notificationBuilder, subscription, notification)
2022-01-05 07:45:02 +13:00
if (notification.attachmentUrl != null) {
val viewIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, Uri.parse(notification.attachmentUrl)), 0)
notificationBuilder
.addAction(NotificationCompat.Action.Builder(0, "Open", viewIntent).build())
.addAction(NotificationCompat.Action.Builder(0, "Copy URL", viewIntent).build())
2022-01-05 12:40:19 +13:00
.addAction(NotificationCompat.Action.Builder(0, "Download", viewIntent).build())
2022-01-05 07:45:02 +13:00
}
2022-01-04 12:54:18 +13:00
notificationBuilder = if (bitmap != null) {
2022-01-05 07:45:02 +13:00
notificationBuilder
.setLargeIcon(bitmap)
.setStyle(NotificationCompat.BigPictureStyle()
.bigPicture(bitmap)
.bigLargeIcon(null))
2022-01-04 12:54:18 +13:00
} else {
notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
}
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
2021-11-30 08:06:08 +13:00
maybeCreateNotificationChannel(notificationManager, notification.priority)
notificationManager.notify(notification.notificationId, notificationBuilder.build())
}
2022-01-05 07:45:02 +13:00
private fun downloadPreviewAndUpdate(subscription: Subscription, notification: Notification) {
val previewUrl = notification.attachmentPreviewUrl ?: return
Log.d(TAG, "Downloading preview image $previewUrl")
val request = Request.Builder()
.url(previewUrl)
.addHeader("User-Agent", ApiService.USER_AGENT)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful || response.body == null) {
Log.d(TAG, "Preview response failed: ${response.code}")
} else {
Log.d(TAG, "Successful response, streaming preview")
val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream())
displayInternal(subscription, notification, bitmap)
}
}
}
2022-01-05 11:45:24 +13:00
private fun setContentIntent(builder: NotificationCompat.Builder, subscription: Subscription, notification: Notification): NotificationCompat.Builder? {
if (notification.click == "") {
return builder.setContentIntent(detailActivityIntent(subscription))
}
return try {
val uri = Uri.parse(notification.click)
val viewIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, uri), 0)
builder.setContentIntent(viewIntent)
} catch (e: Exception) {
builder.setContentIntent(detailActivityIntent(subscription))
}
}
2022-01-05 07:45:02 +13:00
private fun downloadPreviewAndUpdateXXX(subscription: Subscription, notification: Notification) {
2022-01-04 12:54:18 +13:00
val url = notification.attachmentUrl ?: return
2022-01-05 07:45:02 +13:00
Log.d(TAG, "Downloading attachment from $url")
2022-01-04 12:54:18 +13:00
val request = Request.Builder()
.url(url)
.addHeader("User-Agent", ApiService.USER_AGENT)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful || response.body == null) {
2022-01-05 07:45:02 +13:00
Log.d(TAG, "Attachment download failed: ${response.code}")
} else {
Log.d(TAG, "Successful response")
/*val filename = notification.id
val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS + "/ntfy/" + notification.id)
context.openFileOutput(filename, Context.MODE_PRIVATE).use {
response.body!!.byteStream()
}*/
// TODO work manager
val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream())
displayInternal(subscription, notification, bitmap)
2022-01-04 12:54:18 +13:00
}
}
2021-11-30 08:06:08 +13:00
}
2022-01-05 11:45:24 +13:00
private fun detailActivityIntent(subscription: Subscription): PendingIntent? {
val intent = Intent(context, DetailActivity::class.java)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
return TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(intent) // Add the intent, which inflates the back stack
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) // Get the PendingIntent containing the entire back stack
}
}
2021-11-30 08:06:08 +13:00
private fun maybeCreateNotificationChannel(notificationManager: NotificationManager, priority: Int) {
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) {
1 -> NotificationChannel(CHANNEL_ID_MIN, context.getString(R.string.channel_notifications_min_name), NotificationManager.IMPORTANCE_MIN)
2 -> NotificationChannel(CHANNEL_ID_LOW, context.getString(R.string.channel_notifications_low_name), NotificationManager.IMPORTANCE_LOW)
4 -> {
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
}
5 -> {
val channel = NotificationChannel(CHANNEL_ID_MAX, context.getString(R.string.channel_notifications_max_name), NotificationManager.IMPORTANCE_MAX)
channel.enableLights(true)
channel.enableVibration(true)
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-28 10:18:09 +13:00
}
}
private fun toChannelId(priority: Int): String {
return when (priority) {
1 -> CHANNEL_ID_MIN
2 -> CHANNEL_ID_LOW
4 -> CHANNEL_ID_HIGH
5 -> CHANNEL_ID_MAX
else -> CHANNEL_ID_DEFAULT
}
}
companion object {
private const val TAG = "NtfyNotificationService"
2021-11-28 10:18:09 +13: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"
}
}