// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
package app.tauri.notification
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.webkit.WebView
import app.tauri.PermissionState
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Permission
import app.tauri.annotation.PermissionCallback
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSArray
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
const val LOCAL_NOTIFICATIONS = "permissionState"
@InvokeArg
class PluginConfig {
var icon: String? = null
var sound: String? = null
var iconColor: String? = null
}
@InvokeArg
class BatchArgs {
lateinit var notifications: List<Notification>
}
@InvokeArg
class CancelArgs {
lateinit var notifications: List<Int>
}
@InvokeArg
class NotificationAction {
lateinit var id: String
var title: String? = null
var input: Boolean? = null
}
@InvokeArg
class ActionType {
lateinit var id: String
lateinit var actions: List<NotificationAction>
}
@InvokeArg
class RegisterActionTypesArgs {
lateinit var types: List<ActionType>
}
@InvokeArg
class ActiveNotification {
var id: Int = 0
var tag: String? = null
}
@InvokeArg
class RemoveActiveArgs {
var notifications: List<ActiveNotification> = listOf()
}
@TauriPlugin(
permissions = [
Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "permissionState")
]
)
class NotificationPlugin(private val activity: Activity): Plugin(activity) {
private var webView: WebView? = null
private lateinit var manager: TauriNotificationManager
private lateinit var notificationManager: NotificationManager
private lateinit var notificationStorage: NotificationStorage
private var channelManager = ChannelManager(activity)
companion object {
var instance: NotificationPlugin? = null
fun triggerNotification(notification: Notification) {
instance?.triggerObject("notification", notification)
}
}
override fun load(webView: WebView) {
instance = this
super.load(webView)
this.webView = webView
notificationStorage = NotificationStorage(activity, jsonMapper())
val manager = TauriNotificationManager(
notificationStorage,
activity,
activity,
getConfig(PluginConfig::class.java)
)
manager.createNotificationChannel()
this.manager = manager
notificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val intent = activity.intent
intent?.let {
onIntent(it)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
onIntent(intent)
}
fun onIntent(intent: Intent) {
if (Intent.ACTION_MAIN != intent.action) {
return
}
val dataJson = manager.handleNotificationActionPerformed(intent, notificationStorage)
if (dataJson != null) {
trigger("actionPerformed", dataJson)
}
}
@Command
fun show(invoke: Invoke) {
val notification = invoke.parseArgs(Notification::class.java)
val id = manager.schedule(notification)
invoke.resolveObject(id)
}
@Command
fun batch(invoke: Invoke) {
val args = invoke.parseArgs(BatchArgs::class.java)
val ids = manager.schedule(args.notifications)
notificationStorage.appendNotifications(args.notifications)
invoke.resolveObject(ids)
}
@Command
fun cancel(invoke: Invoke) {
val args = invoke.parseArgs(CancelArgs::class.java)
manager.cancel(args.notifications)
invoke.resolve()
}
@Command
fun removeActive(invoke: Invoke) {
val args = invoke.parseArgs(RemoveActiveArgs::class.java)
if (args.notifications.isEmpty()) {
notificationManager.cancelAll()
invoke.resolve()
} else {
for (notification in args.notifications) {
if (notification.tag == null) {
notificationManager.cancel(notification.id)
} else {
notificationManager.cancel(notification.tag, notification.id)
}
}
invoke.resolve()
}
}
@Command
fun getPending(invoke: Invoke) {
val notifications= notificationStorage.getSavedNotifications()
val result = Notification.buildNotificationPendingList(notifications)
invoke.resolveObject(result)
}
@Command
fun registerActionTypes(invoke: Invoke) {
val args = invoke.parseArgs(RegisterActionTypesArgs::class.java)
notificationStorage.writeActionGroup(args.types)
invoke.resolve()
}
@SuppressLint("ObsoleteSdkInt")
@Command
fun getActive(invoke: Invoke) {
val notifications = JSArray()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNotifications = notificationManager.activeNotifications
for (activeNotification in activeNotifications) {
val jsNotification = JSObject()
jsNotification.put("id", activeNotification.id)
jsNotification.put("tag", activeNotification.tag)
val notification = activeNotification.notification
if (notification != null) {
jsNotification.put("title", notification.extras.getCharSequence(android.app.Notification.EXTRA_TITLE))
jsNotification.put("body", notification.extras.getCharSequence(android.app.Notification.EXTRA_TEXT))
jsNotification.put("group", notification.group)
jsNotification.put(
"groupSummary",
0 != notification.flags and android.app.Notification.FLAG_GROUP_SUMMARY
)
val extras = JSObject()
for (key in notification.extras.keySet()) {
extras.put(key!!, notification.extras.getString(key))
}
jsNotification.put("data", extras)
}
notifications.put(jsNotification)
}
}
invoke.resolveObject(notifications)
}
@Command
fun createChannel(invoke: Invoke) {
channelManager.createChannel(invoke)
}
@Command
fun deleteChannel(invoke: Invoke) {
channelManager.deleteChannel(invoke)
}
@Command
fun listChannels(invoke: Invoke) {
channelManager.listChannels(invoke)
}
@Command
override fun checkPermissions(invoke: Invoke) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
val permissionsResultJSON = JSObject()
permissionsResultJSON.put("permissionState", getPermissionState())
invoke.resolve(permissionsResultJSON)
} else {
super.checkPermissions(invoke)
}
}
@Command
override fun requestPermissions(invoke: Invoke) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
permissionState(invoke)
} else {
if (getPermissionState(LOCAL_NOTIFICATIONS) !== PermissionState.GRANTED) {
requestPermissionForAlias(LOCAL_NOTIFICATIONS, invoke, "permissionsCallback")
}
}
}
@Command
fun permissionState(invoke: Invoke) {
val permissionsResultJSON = JSObject()
permissionsResultJSON.put("permissionState", getPermissionState())
invoke.resolve(permissionsResultJSON)
}
@PermissionCallback
private fun permissionsCallback(invoke: Invoke) {
val permissionsResultJSON = JSObject()
permissionsResultJSON.put("permissionState", getPermissionState())
invoke.resolve(permissionsResultJSON)
}
private fun getPermissionState(): String {
return if (manager.areNotificationsEnabled()) {
"granted"
} else {
"denied"
}
}
}