diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e5be012..a8f0279 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
?, // used in "http" action
diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt
index 2c6545b..60c4fa9 100644
--- a/app/src/main/java/io/heckel/ntfy/db/Database.kt
+++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt
@@ -87,6 +87,7 @@ 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, // "view", "http" or "broadcast"
@ColumnInfo(name = "label") val label: String,
+ @ColumnInfo(name = "clear") val clear: Boolean?, // clear notification after successful execution
@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?, // used in "http" action
diff --git a/app/src/main/java/io/heckel/ntfy/msg/Message.kt b/app/src/main/java/io/heckel/ntfy/msg/Message.kt
index e2fcd76..04289ae 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/Message.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/Message.kt
@@ -1,7 +1,6 @@
package io.heckel.ntfy.msg
import androidx.annotation.Keep
-import io.heckel.ntfy.db.Action
/* This annotation ensures that proguard still works in production builds,
* see https://stackoverflow.com/a/62753300/1440785 */
@@ -35,6 +34,7 @@ data class MessageAction(
val id: String,
val action: String,
val label: String, // "view", "broadcast" or "http"
+ val clear: Boolean?, // clear notification after successful execution
val url: String?, // used in "view" and "http" actions
val method: String?, // used in "http" action, default is POST (!)
val headers: Map?, // used in "http" action
diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt
index b855d24..0faaf23 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt
@@ -33,7 +33,20 @@ 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, a.intent, a.extras, null, null)
+ Action(
+ id = a.id,
+ action = a.action,
+ label = a.label,
+ clear = a.clear,
+ url = a.url,
+ method = a.method,
+ headers = a.headers,
+ body = a.body,
+ intent = a.intent,
+ extras = a.extras,
+ progress = null,
+ error = null
+ )
}
} else null
val notification = Notification(
@@ -62,7 +75,20 @@ class NotificationParser {
val listType: Type = object : TypeToken?>() {}.type
val messageActions: List? = gson.fromJson(s, listType)
return messageActions?.map { a ->
- Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.intent, a.extras, null, null)
+ Action(
+ id = a.id,
+ action = a.action,
+ label = a.label,
+ clear = a.clear,
+ url = a.url,
+ method = a.method,
+ headers = a.headers,
+ body = a.body,
+ intent = a.intent,
+ extras = a.extras,
+ progress = null,
+ error = null
+ )
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
index f612993..e0b2e94 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
@@ -201,18 +201,27 @@ class NotificationService(val context: Context) {
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, ACTION_BROADCAST -> maybeAddHttpOrBroadcastUserAction(builder, notification, action)
+ // ACTION_VIEW weirdness:
+ // It's apparently impossible to start an activity from PendingIntent.getActivity() and also close
+ // the notification. To clear it, we have to actually run our own code, which we do via the UserActionWorker.
+ // However, Android has a weird bug that does not allow a BroadcastReceiver or Worker to start an activity
+ // in the foreground and also close the notification drawer, without sending a deprecated Intent. So to not
+ // have to use this deprecated code in the majority case, we do this weird viewActionWithoutClear below.
+ //
+ // See https://stackoverflow.com/questions/18261969/clicking-android-notification-actions-does-not-close-notification-drawer
+
+ val actionType = action.action.lowercase(Locale.getDefault())
+ val viewActionWithoutClear = actionType == ACTION_VIEW && action.clear != true
+ if (viewActionWithoutClear) {
+ addViewUserActionWithoutClear(builder, action)
+ } else {
+ addHttpOrBroadcastUserAction(builder, notification, action)
}
}
}
- private fun maybeAddViewUserAction(builder: NotificationCompat.Builder, action: Action) {
- // Note that this function is (almost) duplicated in DetailAdapter, since we need to be able
- // to open a link from the detail activity as well. We can't do this in the UserActionWorker,
- // because the behavior is kind of weird in Android.
-
+ private fun addViewUserActionWithoutClear(builder: NotificationCompat.Builder, action: Action) {
+ Log.d(TAG, "Adding view action (no clear) for ${action.url}")
try {
val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
@@ -225,7 +234,7 @@ class NotificationService(val context: Context) {
}
}
- private fun maybeAddHttpOrBroadcastUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) {
+ private fun addHttpOrBroadcastUserAction(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)
@@ -318,7 +327,7 @@ class NotificationService(val context: Context) {
const val BROADCAST_EXTRA_TYPE = "type"
const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId"
- const val BROADCAST_EXTRA_ACTION_ID = "action"
+ const val BROADCAST_EXTRA_ACTION_ID = "actionId"
const val BROADCAST_TYPE_DOWNLOAD_START = "io.heckel.ntfy.DOWNLOAD_ACTION_START"
const val BROADCAST_TYPE_DOWNLOAD_CANCEL = "io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL"
diff --git a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt
index d505813..6cdca33 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt
@@ -1,6 +1,8 @@
package io.heckel.ntfy.msg
import android.content.Context
+import android.content.Intent
+import android.net.Uri
import androidx.work.Worker
import androidx.work.WorkerParameters
import io.heckel.ntfy.R
@@ -8,6 +10,7 @@ import io.heckel.ntfy.app.Application
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.msg.NotificationService.Companion.ACTION_VIEW
import io.heckel.ntfy.util.Log
import okhttp3.OkHttpClient
import okhttp3.Request
@@ -46,6 +49,7 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
// ACTION_VIEW is not handled here. It has to be handled in the foreground to avoid
// weird Android behavior.
+ ACTION_VIEW -> performViewAction(action)
ACTION_BROADCAST -> performBroadcastAction(action)
ACTION_HTTP -> performHttpAction(action)
}
@@ -59,8 +63,31 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
return Result.success()
}
+ private fun performViewAction(action: Action) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)).apply {
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ context.startActivity(intent)
+ if (action.clear == true) {
+ notifier.cancel(notification)
+ }
+
+ // Close notification drawer. This seems to be a bug in Android that when a new activity is started from
+ // a receiver or worker, the drawer does not close. Using this deprecated intent is the only option I have found.
+ //
+ // See https://stackoverflow.com/questions/18261969/clicking-android-notification-actions-does-not-close-notification-drawer
+ try {
+ context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ } catch (e: Exception) {
+ Log.w(TAG, "Cannot close system dialogs", e)
+ }
+ }
+
private fun performBroadcastAction(action: Action) {
broadcaster.sendUserAction(action)
+ if (action.clear == true) {
+ notifier.cancel(notification)
+ }
}
private fun performHttpAction(action: Action) {
@@ -90,12 +117,17 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
private fun save(newAction: Action) {
Log.d(TAG, "Updating action: $newAction")
+ val clear = newAction.progress == ACTION_PROGRESS_SUCCESS && action.clear == true
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)
+ if (clear) {
+ notifier.cancel(notification)
+ } else {
+ notifier.update(subscription, notification)
+ }
}
companion object {