// 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 } @InvokeArg class CancelArgs { lateinit var notifications: List } @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 } @InvokeArg class RegisterActionTypesArgs { lateinit var types: List } @InvokeArg class ActiveNotification { var id: Int = 0 var tag: String? = null } @InvokeArg class RemoveActiveArgs { var notifications: List = 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("display", getPermissionState()) invoke.resolve(permissionsResultJSON) } private fun getPermissionState(): String { return if (manager.areNotificationsEnabled()) { "granted" } else { "denied" } } }