mirror of
https://github.com/binwiederhier/ntfy-android.git
synced 2024-05-21 04:52:32 +12:00
Actions WIP
This commit is contained in:
parent
686616d4d2
commit
79c0e91e8d
|
@ -73,20 +73,33 @@ data class Attachment(
|
|||
@ColumnInfo(name = "progress") val progress: Int, // Progress during download, -1 if not downloaded
|
||||
) {
|
||||
constructor(name: String, type: String?, size: Long?, expires: Long?, url: String) :
|
||||
this(name, type, size, expires, url, null, PROGRESS_NONE)
|
||||
this(name, type, size, expires, url, null, ATTACHMENT_PROGRESS_NONE)
|
||||
}
|
||||
|
||||
const val ATTACHMENT_PROGRESS_NONE = -1
|
||||
const val ATTACHMENT_PROGRESS_INDETERMINATE = -2
|
||||
const val ATTACHMENT_PROGRESS_FAILED = -3
|
||||
const val ATTACHMENT_PROGRESS_DELETED = -4
|
||||
const val ATTACHMENT_PROGRESS_DONE = 100
|
||||
|
||||
@Entity
|
||||
data class Action(
|
||||
@ColumnInfo(name = "id") val id: String, // Synthetic ID to identify result, and easily pass via Broadcast and WorkManager
|
||||
@ColumnInfo(name = "action") val action: String,
|
||||
@ColumnInfo(name = "action") val action: String, // "view", "http" or "broadcast"
|
||||
@ColumnInfo(name = "label") val label: String,
|
||||
@ColumnInfo(name = "url") val url: String?, // used in "view" and "http"
|
||||
@ColumnInfo(name = "method") val method: String?, // used in "http"
|
||||
@ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http"
|
||||
@ColumnInfo(name = "body") val body: String?, // used in "http"
|
||||
@ColumnInfo(name = "url") val url: String?, // used in "view" and "http" actions
|
||||
@ColumnInfo(name = "method") val method: String?, // used in "http" action
|
||||
@ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http" action
|
||||
@ColumnInfo(name = "body") val body: String?, // used in "http" action
|
||||
@ColumnInfo(name = "extras") val extras: Map<String,String>?, // used in "broadcast" action
|
||||
@ColumnInfo(name = "progress") val progress: Int?, // used to indicate progress in popup
|
||||
@ColumnInfo(name = "error") val error: String?, // used to indicate errors in popup
|
||||
)
|
||||
|
||||
const val ACTION_PROGRESS_ONGOING = 1
|
||||
const val ACTION_PROGRESS_SUCCESS = 2
|
||||
const val ACTION_PROGRESS_FAILED = 3
|
||||
|
||||
class Converters {
|
||||
private val gson = Gson()
|
||||
|
||||
|
@ -102,12 +115,6 @@ class Converters {
|
|||
}
|
||||
}
|
||||
|
||||
const val PROGRESS_NONE = -1
|
||||
const val PROGRESS_INDETERMINATE = -2
|
||||
const val PROGRESS_FAILED = -3
|
||||
const val PROGRESS_DELETED = -4
|
||||
const val PROGRESS_DONE = 100
|
||||
|
||||
@Entity
|
||||
data class User(
|
||||
@PrimaryKey @ColumnInfo(name = "baseUrl") val baseUrl: String,
|
||||
|
|
|
@ -2,8 +2,8 @@ package io.heckel.ntfy.msg
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Base64
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.db.Action
|
||||
import io.heckel.ntfy.db.Notification
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
|
@ -17,7 +17,7 @@ import kotlinx.coroutines.launch
|
|||
* in order to facilitate tasks app integrations.
|
||||
*/
|
||||
class BroadcastService(private val ctx: Context) {
|
||||
fun send(subscription: Subscription, notification: Notification, muted: Boolean) {
|
||||
fun sendMessage(subscription: Subscription, notification: Notification, muted: Boolean) {
|
||||
val intent = Intent()
|
||||
intent.action = MESSAGE_RECEIVED_ACTION
|
||||
intent.putExtra("id", notification.id)
|
||||
|
@ -34,7 +34,17 @@ class BroadcastService(private val ctx: Context) {
|
|||
intent.putExtra("muted", muted)
|
||||
intent.putExtra("muted_str", muted.toString())
|
||||
|
||||
Log.d(TAG, "Sending intent broadcast: $intent")
|
||||
Log.d(TAG, "Sending message intent broadcast: $intent")
|
||||
ctx.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
fun sendUserAction(action: Action) {
|
||||
val intent = Intent()
|
||||
intent.action = USER_ACTION_ACTION
|
||||
action.extras?.forEach { (key, value) ->
|
||||
intent.putExtra(key, value)
|
||||
}
|
||||
Log.d(TAG, "Sending user action intent broadcast: $intent")
|
||||
ctx.sendBroadcast(intent)
|
||||
}
|
||||
|
||||
|
@ -109,5 +119,6 @@ class BroadcastService(private val ctx: Context) {
|
|||
// These constants cannot be changed without breaking the contract; also see manifest
|
||||
private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
|
||||
private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE"
|
||||
private const val USER_ACTION_ACTION = "io.heckel.ntfy.USER_ACTION"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,13 +91,13 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
|
|||
while (bytes >= 0) {
|
||||
if (System.currentTimeMillis() - lastProgress > NOTIFICATION_UPDATE_INTERVAL_MILLIS) {
|
||||
if (isStopped) { // Canceled by user
|
||||
save(attachment.copy(progress = PROGRESS_NONE))
|
||||
save(attachment.copy(progress = ATTACHMENT_PROGRESS_NONE))
|
||||
return // File will be deleted in onStopped()
|
||||
}
|
||||
val progress = if (attachment.size != null && attachment.size!! > 0) {
|
||||
(bytesCopied.toFloat()/attachment.size!!.toFloat()*100).toInt()
|
||||
} else {
|
||||
PROGRESS_INDETERMINATE
|
||||
ATTACHMENT_PROGRESS_INDETERMINATE
|
||||
}
|
||||
save(attachment.copy(progress = progress))
|
||||
lastProgress = System.currentTimeMillis()
|
||||
|
@ -114,7 +114,7 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
|
|||
save(attachment.copy(
|
||||
size = bytesCopied,
|
||||
contentUri = uri.toString(),
|
||||
progress = PROGRESS_DONE
|
||||
progress = ATTACHMENT_PROGRESS_DONE
|
||||
))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -155,7 +155,7 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W
|
|||
|
||||
private fun failed(e: Exception) {
|
||||
Log.w(TAG, "Attachment download failed", e)
|
||||
save(attachment.copy(progress = PROGRESS_FAILED))
|
||||
save(attachment.copy(progress = ATTACHMENT_PROGRESS_FAILED))
|
||||
maybeDeleteFile()
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,12 @@ data class MessageAttachment(
|
|||
data class MessageAction(
|
||||
val id: String,
|
||||
val action: String,
|
||||
val label: String,
|
||||
val label: String, // "view", "broadcast" or "http"
|
||||
val url: String?, // used in "view" and "http"
|
||||
val method: String?, // used in "http"
|
||||
val method: String?, // used in "http", default is POST (!)
|
||||
val headers: Map<String,String>?, // used in "http"
|
||||
val body: String?, // used in "http"
|
||||
val extras: Map<String,String>?, // used in "broadcast"
|
||||
)
|
||||
|
||||
const val MESSAGE_ENCODING_BASE64 = "base64"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.heckel.ntfy.msg
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import io.heckel.ntfy.db.Notification
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.db.Subscription
|
||||
|
@ -35,7 +34,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
|
|||
notifier.display(subscription, notification)
|
||||
}
|
||||
if (broadcast) {
|
||||
broadcaster.send(subscription, notification, muted)
|
||||
broadcaster.sendMessage(subscription, notification, muted)
|
||||
}
|
||||
if (distribute) {
|
||||
safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken ->
|
||||
|
|
|
@ -33,7 +33,7 @@ class NotificationParser {
|
|||
} else null
|
||||
val actions = if (message.actions != null) {
|
||||
message.actions.map { a ->
|
||||
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body)
|
||||
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.extras, null, null)
|
||||
}
|
||||
} else null
|
||||
val notification = Notification(
|
||||
|
|
|
@ -66,7 +66,7 @@ class NotificationService(val context: Context) {
|
|||
maybeAddBrowseAction(builder, notification)
|
||||
maybeAddDownloadAction(builder, notification)
|
||||
maybeAddCancelAction(builder, notification)
|
||||
maybeAddCustomActions(builder, notification)
|
||||
maybeAddUserActions(builder, notification)
|
||||
|
||||
maybeCreateNotificationChannel(notification.priority)
|
||||
notificationManager.notify(notification.notificationId, builder.build())
|
||||
|
@ -90,43 +90,55 @@ class NotificationService(val context: Context) {
|
|||
val bitmapStream = resolver.openInputStream(Uri.parse(contentUri))
|
||||
val bitmap = BitmapFactory.decodeStream(bitmapStream)
|
||||
builder
|
||||
.setContentText(formatMessage(notification))
|
||||
.setContentText(maybeAppendActionErrors(formatMessage(notification), notification))
|
||||
.setLargeIcon(bitmap)
|
||||
.setStyle(NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(bitmap)
|
||||
.bigLargeIcon(null))
|
||||
} catch (_: Exception) {
|
||||
val message = formatMessageMaybeWithAttachmentInfo(notification)
|
||||
val message = maybeAppendActionErrors(formatMessageMaybeWithAttachmentInfos(notification), notification)
|
||||
builder
|
||||
.setContentText(message)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||
}
|
||||
} else {
|
||||
val message = formatMessageMaybeWithAttachmentInfo(notification)
|
||||
val message = maybeAppendActionErrors(formatMessageMaybeWithAttachmentInfos(notification), notification)
|
||||
builder
|
||||
.setContentText(message)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatMessageMaybeWithAttachmentInfo(notification: Notification): String {
|
||||
private fun formatMessageMaybeWithAttachmentInfos(notification: Notification): String {
|
||||
val message = formatMessage(notification)
|
||||
val attachment = notification.attachment ?: return message
|
||||
val infos = if (attachment.size != null) {
|
||||
val attachmentInfos = if (attachment.size != null) {
|
||||
"${attachment.name}, ${formatBytes(attachment.size)}"
|
||||
} else {
|
||||
attachment.name
|
||||
}
|
||||
if (attachment.progress in 0..99) {
|
||||
return context.getString(R.string.notification_popup_file_downloading, infos, attachment.progress, message)
|
||||
return context.getString(R.string.notification_popup_file_downloading, attachmentInfos, attachment.progress, message)
|
||||
}
|
||||
if (attachment.progress == PROGRESS_DONE) {
|
||||
return context.getString(R.string.notification_popup_file_download_successful, message, infos)
|
||||
if (attachment.progress == ATTACHMENT_PROGRESS_DONE) {
|
||||
return context.getString(R.string.notification_popup_file_download_successful, message, attachmentInfos)
|
||||
}
|
||||
if (attachment.progress == PROGRESS_FAILED) {
|
||||
return context.getString(R.string.notification_popup_file_download_failed, message, infos)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun maybeAppendActionErrors(message: String, notification: Notification): String {
|
||||
val actionErrors = notification.actions
|
||||
.orEmpty()
|
||||
.mapNotNull { action -> action.error }
|
||||
.joinToString("\n")
|
||||
if (actionErrors.isEmpty()) {
|
||||
return message
|
||||
} else {
|
||||
return "${message}\n\n${actionErrors}"
|
||||
}
|
||||
return context.getString(R.string.notification_popup_file, message, infos)
|
||||
}
|
||||
|
||||
private fun setClickAction(builder: NotificationCompat.Builder, subscription: Subscription, notification: Notification) {
|
||||
|
@ -135,7 +147,7 @@ class NotificationService(val context: Context) {
|
|||
} else {
|
||||
try {
|
||||
val uri = Uri.parse(notification.click)
|
||||
val viewIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW, uri), PendingIntent.FLAG_IMMUTABLE)
|
||||
val viewIntent = PendingIntent.getActivity(context, Random().nextInt(), Intent(Intent.ACTION_VIEW, uri), PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.setContentIntent(viewIntent)
|
||||
} catch (e: Exception) {
|
||||
builder.setContentIntent(detailActivityIntent(subscription))
|
||||
|
@ -159,7 +171,7 @@ class NotificationService(val context: Context) {
|
|||
setDataAndType(contentUri, notification.attachment.type ?: "application/octet-stream") // Required for Android <= P
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_open), pendingIntent).build())
|
||||
}
|
||||
}
|
||||
|
@ -169,18 +181,18 @@ class NotificationService(val context: Context) {
|
|||
val intent = Intent(android.app.DownloadManager.ACTION_VIEW_DOWNLOADS).apply {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val pendingIntent = PendingIntent.getActivity(context, Random().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_browse), pendingIntent).build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddDownloadAction(builder: NotificationCompat.Builder, notification: Notification) {
|
||||
if (notification.attachment?.contentUri == null && listOf(PROGRESS_NONE, PROGRESS_FAILED).contains(notification.attachment?.progress)) {
|
||||
if (notification.attachment?.contentUri == null && listOf(ATTACHMENT_PROGRESS_NONE, ATTACHMENT_PROGRESS_FAILED).contains(notification.attachment?.progress)) {
|
||||
val intent = Intent(context, UserActionBroadcastReceiver::class.java).apply {
|
||||
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_DOWNLOAD_START)
|
||||
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_download), pendingIntent).build())
|
||||
}
|
||||
}
|
||||
|
@ -191,47 +203,51 @@ class NotificationService(val context: Context) {
|
|||
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_DOWNLOAD_CANCEL)
|
||||
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, context.getString(R.string.notification_popup_action_cancel), pendingIntent).build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddCustomActions(builder: NotificationCompat.Builder, notification: Notification) {
|
||||
private fun maybeAddUserActions(builder: NotificationCompat.Builder, notification: Notification) {
|
||||
notification.actions?.forEach { action ->
|
||||
when (action.action.lowercase(Locale.getDefault())) {
|
||||
ACTION_VIEW -> maybeAddViewUserAction(builder, action)
|
||||
ACTION_HTTP -> maybeAddHttpUserAction(builder, notification, action)
|
||||
ACTION_HTTP, ACTION_BROADCAST -> maybeAddHttpOrBroadcastUserAction(builder, notification, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddViewUserAction(builder: NotificationCompat.Builder, action: Action) {
|
||||
Log.d(TAG, "Adding user action $action")
|
||||
try {
|
||||
val url = action.url ?: return
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddHttpUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
|
||||
private fun maybeAddHttpOrBroadcastUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
|
||||
val intent = Intent(context, UserActionBroadcastReceiver::class.java).apply {
|
||||
putExtra(BROADCAST_EXTRA_TYPE, BROADCAST_TYPE_USER_ACTION)
|
||||
putExtra(BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
|
||||
putExtra(BROADCAST_EXTRA_ACTION_ID, action.id)
|
||||
}
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, action.label, pendingIntent).build())
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
val label = when (action.progress) {
|
||||
ACTION_PROGRESS_ONGOING -> action.label + " …"
|
||||
ACTION_PROGRESS_SUCCESS -> action.label + " ✔️"
|
||||
ACTION_PROGRESS_FAILED -> action.label + " ❌️"
|
||||
else -> action.label
|
||||
}
|
||||
builder.addAction(NotificationCompat.Action.Builder(0, label, pendingIntent).build())
|
||||
}
|
||||
|
||||
class UserActionBroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Log.d(TAG, "Notification user action intent received: $intent")
|
||||
val type = intent.getStringExtra(BROADCAST_EXTRA_TYPE) ?: return
|
||||
val notificationId = intent.getStringExtra(BROADCAST_EXTRA_NOTIFICATION_ID) ?: return
|
||||
when (type) {
|
||||
|
@ -306,19 +322,19 @@ class NotificationService(val context: Context) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val ACTION_VIEW = "view"
|
||||
val ACTION_HTTP = "http"
|
||||
val ACTION_BROADCAST = "broadcast"
|
||||
|
||||
private const val TAG = "NtfyNotifService"
|
||||
|
||||
private const val BROADCAST_EXTRA_TYPE = "type"
|
||||
private const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId"
|
||||
private const val BROADCAST_EXTRA_ACTION_ID = "action"
|
||||
private const val BROADCAST_EXTRA_ACTION_JSON = "actionJson"
|
||||
|
||||
private const val BROADCAST_TYPE_DOWNLOAD_START = "io.heckel.ntfy.DOWNLOAD_ACTION_START"
|
||||
private const val BROADCAST_TYPE_DOWNLOAD_CANCEL = "io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL"
|
||||
private const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION"
|
||||
|
||||
private const val ACTION_VIEW = "view"
|
||||
private const val ACTION_HTTP = "http"
|
||||
private const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION_RUN"
|
||||
|
||||
private const val CHANNEL_ID_MIN = "ntfy-min"
|
||||
private const val CHANNEL_ID_LOW = "ntfy-low"
|
||||
|
|
|
@ -3,55 +3,99 @@ package io.heckel.ntfy.msg
|
|||
import android.content.Context
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import io.heckel.ntfy.R
|
||||
import io.heckel.ntfy.app.Application
|
||||
import io.heckel.ntfy.db.Action
|
||||
import io.heckel.ntfy.db.*
|
||||
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST
|
||||
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP
|
||||
import io.heckel.ntfy.util.Log
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class UserActionWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
private val client = OkHttpClient.Builder()
|
||||
.callTimeout(15, TimeUnit.MINUTES) // Total timeout for entire request
|
||||
.callTimeout(60, TimeUnit.SECONDS) // Total timeout for entire request
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(15, TimeUnit.SECONDS)
|
||||
.build()
|
||||
private val notifier = NotificationService(context)
|
||||
private val broadcaster = BroadcastService(context)
|
||||
private lateinit var repository: Repository
|
||||
private lateinit var subscription: Subscription
|
||||
private lateinit var notification: Notification
|
||||
private lateinit var action: Action
|
||||
|
||||
override fun doWork(): Result {
|
||||
if (context.applicationContext !is Application) return Result.failure()
|
||||
val notificationId = inputData.getString(INPUT_DATA_NOTIFICATION_ID) ?: return Result.failure()
|
||||
val actionId = inputData.getString(INPUT_DATA_ACTION_ID) ?: return Result.failure()
|
||||
val app = context.applicationContext as Application
|
||||
val notification = app.repository.getNotification(notificationId) ?: return Result.failure()
|
||||
val action = notification.actions?.first { it.id == actionId } ?: return Result.failure()
|
||||
|
||||
repository = app.repository
|
||||
notification = repository.getNotification(notificationId) ?: return Result.failure()
|
||||
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
|
||||
action = notification.actions?.first { it.id == actionId } ?: return Result.failure()
|
||||
|
||||
Log.d(TAG, "Executing action $action for notification $notification")
|
||||
http(context, action)
|
||||
|
||||
try {
|
||||
when (action.action) {
|
||||
ACTION_HTTP -> performHttpAction(action)
|
||||
ACTION_BROADCAST -> performBroadcastAction(action)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error executing action: ${e.message}", e)
|
||||
save(action.copy(
|
||||
progress = ACTION_PROGRESS_FAILED,
|
||||
error = context.getString(R.string.notification_popup_user_action_failed, action.label, e.message)
|
||||
))
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun performHttpAction(action: Action) {
|
||||
save(action.copy(progress = ACTION_PROGRESS_ONGOING, error = null))
|
||||
|
||||
fun http(context: Context, action: Action) { // FIXME Worker!
|
||||
val url = action.url ?: return
|
||||
val method = action.method ?: "GET"
|
||||
val method = action.method ?: "POST" // (not GET, because POST as a default makes more sense!)
|
||||
val body = action.body ?: ""
|
||||
Log.d(TAG, "HTTP POST againt ${action.url}")
|
||||
val request = Request.Builder()
|
||||
val builder = Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", ApiService.USER_AGENT)
|
||||
.method(method, body.toRequestBody())
|
||||
.build()
|
||||
.addHeader("User-Agent", ApiService.USER_AGENT)
|
||||
action.headers?.forEach { (key, value) ->
|
||||
builder.addHeader(key, value)
|
||||
}
|
||||
val request = builder.build()
|
||||
|
||||
Log.d(TAG, "Executing HTTP request: ${method.uppercase(Locale.getDefault())} ${action.url}")
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) {
|
||||
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
|
||||
return
|
||||
}
|
||||
throw Exception("Unexpected server response ${response.code}")
|
||||
throw Exception("HTTP ${response.code}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun performBroadcastAction(action: Action) {
|
||||
broadcaster.sendUserAction(action)
|
||||
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
|
||||
}
|
||||
|
||||
private fun save(newAction: Action) {
|
||||
Log.d(TAG, "Updating action: $newAction")
|
||||
val newActions = notification.actions?.map { a -> if (a.id == newAction.id) newAction else a }
|
||||
val newNotification = notification.copy(actions = newActions)
|
||||
action = newAction
|
||||
notification = newNotification
|
||||
notifier.update(subscription, notification)
|
||||
repository.updateNotification(notification)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INPUT_DATA_NOTIFICATION_ID = "notificationId"
|
||||
const val INPUT_DATA_ACTION_ID = "actionId"
|
||||
|
|
|
@ -217,10 +217,10 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
|||
|
||||
private fun formatAttachmentDetails(context: Context, attachment: Attachment, exists: Boolean): String {
|
||||
val name = attachment.name
|
||||
val notYetDownloaded = !exists && attachment.progress == PROGRESS_NONE
|
||||
val notYetDownloaded = !exists && attachment.progress == ATTACHMENT_PROGRESS_NONE
|
||||
val downloading = !exists && attachment.progress in 0..99
|
||||
val deleted = !exists && (attachment.progress == PROGRESS_DONE || attachment.progress == PROGRESS_DELETED)
|
||||
val failed = !exists && attachment.progress == PROGRESS_FAILED
|
||||
val deleted = !exists && (attachment.progress == ATTACHMENT_PROGRESS_DONE || attachment.progress == ATTACHMENT_PROGRESS_DELETED)
|
||||
val failed = !exists && attachment.progress == ATTACHMENT_PROGRESS_FAILED
|
||||
val expired = attachment.expires != null && attachment.expires < System.currentTimeMillis()/1000
|
||||
val expires = attachment.expires != null && attachment.expires > System.currentTimeMillis()/1000
|
||||
val infos = mutableListOf<String>()
|
||||
|
@ -357,7 +357,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
|
|||
if (!deleted) throw Exception("no rows deleted")
|
||||
val newAttachment = attachment.copy(
|
||||
contentUri = null,
|
||||
progress = PROGRESS_DELETED
|
||||
progress = ATTACHMENT_PROGRESS_DELETED
|
||||
)
|
||||
val newNotification = notification.copy(attachment = newAttachment)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.net.Uri
|
|||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import io.heckel.ntfy.BuildConfig
|
||||
import io.heckel.ntfy.db.PROGRESS_DELETED
|
||||
import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
|
||||
import io.heckel.ntfy.db.Repository
|
||||
import io.heckel.ntfy.ui.DetailAdapter
|
||||
import io.heckel.ntfy.util.Log
|
||||
|
@ -48,7 +48,7 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
|
|||
}
|
||||
val newAttachment = attachment.copy(
|
||||
contentUri = null,
|
||||
progress = PROGRESS_DELETED
|
||||
progress = ATTACHMENT_PROGRESS_DELETED
|
||||
)
|
||||
val newNotification = notification.copy(attachment = newAttachment)
|
||||
repository.updateNotification(newNotification)
|
||||
|
|
|
@ -220,6 +220,7 @@
|
|||
<string name="notification_popup_file_downloading">Downloading %1$s, %2$d%%\n%3$s</string>
|
||||
<string name="notification_popup_file_download_successful">%1$s\nFile: %2$s, downloaded</string>
|
||||
<string name="notification_popup_file_download_failed">%1$s\nFile: %2$s, download failed</string>
|
||||
<string name="notification_popup_user_action_failed">"%1$s" failed: %2$s</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Settings</string>
|
||||
|
|
Loading…
Reference in a new issue