Add clear=true support
This commit is contained in:
parent
b2c6abcfd1
commit
f15d7654c8
7 changed files with 86 additions and 15 deletions
|
@ -10,6 +10,7 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <!-- To install packages downloaded through ntfy; craazyy! -->
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
|
||||
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/> <!-- To close the notification drawer, see UserActionWorker -->
|
||||
|
||||
<application
|
||||
android:name=".app.Application"
|
||||
|
|
|
@ -2,7 +2,6 @@ package io.heckel.ntfy.backup
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.room.ColumnInfo
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.stream.JsonReader
|
||||
|
@ -116,6 +115,7 @@ class Backuper(val context: Context) {
|
|||
id = a.id,
|
||||
action = a.action,
|
||||
label = a.label,
|
||||
clear = a.clear,
|
||||
url = a.url,
|
||||
method = a.method,
|
||||
headers = a.headers,
|
||||
|
@ -228,6 +228,7 @@ class Backuper(val context: Context) {
|
|||
id = a.id,
|
||||
action = a.action,
|
||||
label = a.label,
|
||||
clear = a.clear,
|
||||
url = a.url,
|
||||
method = a.method,
|
||||
headers = a.headers,
|
||||
|
@ -340,6 +341,7 @@ data class Action(
|
|||
val id: String, // Synthetic ID to identify result, and easily pass via Broadcast and WorkManager
|
||||
val action: String, // "view", "http" or "broadcast"
|
||||
val label: String,
|
||||
val clear: Boolean?, // clear notification after successful execution
|
||||
val url: String?, // used in "view" and "http" actions
|
||||
val method: String?, // used in "http" action
|
||||
val headers: Map<String,String>?, // used in "http" action
|
||||
|
|
|
@ -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<String,String>?, // used in "http" action
|
||||
|
|
|
@ -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<String,String>?, // used in "http" action
|
||||
|
|
|
@ -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<List<MessageAction>?>() {}.type
|
||||
val messageActions: List<MessageAction>? = 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue