diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4cc3892..4ea7864 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,209 +1,205 @@ if (Phonon4Qt5_FOUND) include_directories(${PHONON_INCLUDE_DIR}) endif() if (CANBERRA_FOUND) include_directories(${CANBERRA_INCLUDE_DIRS}) endif() ecm_create_qm_loader(knotifications_QM_LOADER knotifications5_qt) set(knotifications_SRCS knotification.cpp knotificationmanager.cpp kpassivepopup.cpp knotifyconfig.cpp knotificationplugin.cpp notifybypopupgrowl.cpp notifybyexecute.cpp notifybylogfile.cpp notifybytaskbar.cpp ${knotifications_QM_LOADER} ) if (TARGET Qt5::DBus) list(APPEND knotifications_SRCS kstatusnotifieritem.cpp kstatusnotifieritemdbus_p.cpp knotificationrestrictions.cpp imageconverter.cpp #needed to marshal images for sending over dbus by NotifyByPopup notifybypopup.cpp notifybyportal.cpp ) endif() if (ANDROID) - if (NOT CMAKE_ANDROID_API VERSION_LESS 23) add_subdirectory(android) list(APPEND knotifications_SRCS notifybyandroid.cpp knotifications.qrc) - else() - message(WARNING "Android notification backend needs at least API level 23!") - endif() endif() ecm_qt_declare_logging_category(knotifications_SRCS HEADER debug_p.h IDENTIFIER LOG_KNOTIFICATIONS CATEGORY_NAME org.kde.knotifications) if (CANBERRA_FOUND) set(knotifications_SRCS ${knotifications_SRCS} notifybyaudio_canberra.cpp) elseif (Phonon4Qt5_FOUND) set(knotifications_SRCS ${knotifications_SRCS} notifybyaudio_phonon.cpp) endif() if (Qt5TextToSpeech_FOUND) set(knotifications_SRCS ${knotifications_SRCS} notifybytts.cpp) endif() find_package(dbusmenu-qt5 CONFIG) set_package_properties(dbusmenu-qt5 PROPERTIES DESCRIPTION "DBusMenuQt" URL "https://launchpad.net/libdbusmenu-qt" TYPE OPTIONAL PURPOSE "Support for notification area menus via the DBusMenu protocol") if (dbusmenu-qt5_FOUND) message("dbusmenu-qt5_FOUND") set(HAVE_DBUSMENUQT 1) include_directories(${dbusmenu-qt5_INCLUDE_DIRS}) else() set(HAVE_DBUSMENUQT 0) endif() if (TARGET Qt5::DBus) qt5_add_dbus_adaptor(knotifications_SRCS org.kde.StatusNotifierItem.xml kstatusnotifieritemdbus_p.h KStatusNotifierItemDBus) set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml) qt5_add_dbus_interface(knotifications_SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface) set(notifications_xml org.freedesktop.Notifications.xml) qt5_add_dbus_interface(knotifications_SRCS ${notifications_xml} notifications_interface) endif() configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h ) add_library(KF5Notifications ${knotifications_SRCS}) generate_export_header(KF5Notifications BASE_NAME KNotifications) add_library(KF5::Notifications ALIAS KF5Notifications) target_include_directories(KF5Notifications INTERFACE "$") target_link_libraries(KF5Notifications PUBLIC Qt5::Widgets ) if (TARGET Qt5::DBus) target_link_libraries(KF5Notifications PUBLIC Qt5::DBus) endif() target_link_libraries(KF5Notifications PRIVATE KF5::CoreAddons KF5::ConfigCore KF5::WindowSystem KF5::Codecs ) if (Phonon4Qt5_FOUND) target_link_libraries(KF5Notifications PRIVATE ${PHONON_LIBRARIES}) endif() if (CANBERRA_FOUND) target_link_libraries(KF5Notifications PRIVATE ${CANBERRA_LIBRARIES}) endif() if (Qt5TextToSpeech_FOUND) target_link_libraries(KF5Notifications PRIVATE Qt5::TextToSpeech) endif() if(X11_FOUND) target_link_libraries(KF5Notifications PRIVATE ${X11_X11_LIB} Qt5::X11Extras) endif() if(APPLE) target_link_libraries(KF5Notifications PRIVATE Qt5::MacExtras) endif() if(X11_XTest_FOUND) target_link_libraries(KF5Notifications PRIVATE ${X11_XTest_LIB}) endif() if(HAVE_DBUSMENUQT) target_link_libraries(KF5Notifications PRIVATE dbusmenu-qt5) endif() if (ANDROID) target_link_libraries(KF5Notifications PRIVATE Qt5::AndroidExtras) endif() set_target_properties(KF5Notifications PROPERTIES VERSION ${KNOTIFICATIONS_VERSION_STRING} SOVERSION ${KNOTIFICATIONS_SOVERSION} EXPORT_NAME Notifications ) ecm_generate_headers(KNotifications_HEADERS HEADER_NAMES KNotification KPassivePopup KStatusNotifierItem KNotificationRestrictions KNotificationPlugin KNotifyConfig REQUIRED_HEADERS KNotifications_HEADERS ) install(TARGETS KF5Notifications EXPORT KF5NotificationsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/knotifications_export.h ${KNotifications_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications COMPONENT Devel ) if(BUILD_QCH) ecm_add_qch( KF5Notifications_QCH NAME KNotifications BASE_NAME KF5Notifications VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KNotifications_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS Qt5Widgets_QCH BLANK_MACROS KNOTIFICATIONS_EXPORT KNOTIFICATIONS_DEPRECATED KNOTIFICATIONS_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() if (TARGET Qt5::DBus) install(FILES org.kde.StatusNotifierItem.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.StatusNotifierItem.xml) install(FILES org.kde.StatusNotifierWatcher.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.StatusNotifierWatcher.xml) endif() install(FILES knotificationplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KNotifications LIB_NAME KF5Notifications DEPS "widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KNotifications) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) -if (ANDROID AND NOT ANDROID_API_LEVEL VERSION_LESS 23) +if (ANDROID) install(FILES KF5Notifications-android-dependencies.xml DESTINATION ${KDE_INSTALL_LIBDIR}) endif() diff --git a/src/android/org/kde/knotifications/KNotification.java b/src/android/org/kde/knotifications/KNotification.java index 62cc1b1..2852d59 100644 --- a/src/android/org/kde/knotifications/KNotification.java +++ b/src/android/org/kde/knotifications/KNotification.java @@ -1,43 +1,48 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package org.kde.knotifications; import android.graphics.drawable.Icon; +import android.os.Build; + +import java.lang.Object; import java.util.ArrayList; /** Java side of KNotification. * Used to convey the relevant notification data to Java. */ public class KNotification { public int id; public String text; public String title; - public Icon icon; + public Object icon; public ArrayList actions = new ArrayList(); public void setIconFromData(byte[] data, int length) { - icon = Icon.createWithData(data, 0, length); + if (Build.VERSION.SDK_INT >= 23) { + icon = Icon.createWithData(data, 0, length); + } } public void addAction(String action) { actions.add(action); } } diff --git a/src/android/org/kde/knotifications/NotifyByAndroid.java b/src/android/org/kde/knotifications/NotifyByAndroid.java index 2bfba79..5ae26f2 100644 --- a/src/android/org/kde/knotifications/NotifyByAndroid.java +++ b/src/android/org/kde/knotifications/NotifyByAndroid.java @@ -1,113 +1,117 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package org.kde.knotifications; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.os.Build; import android.util.Log; /** Java side of the Android notfication backend. */ public class NotifyByAndroid extends BroadcastReceiver { private static final String TAG = "org.kde.knotifications"; private static final String NOTIFICATION_ACTION = ".org.kde.knotifications.NOTIFICATION_ACTION"; private static final String NOTIFICATION_DELETED = ".org.kde.knotifications.NOTIFICATION_DELETED"; private static final String NOTIFICATION_ID_EXTRA = "org.kde.knotifications.NOTIFICATION_ID"; private static final String NOTIFICATION_ACTION_ID_EXTRA = "org.kde.knotifications.NOTIFICATION_ACTION_ID"; private android.content.Context m_ctx; private NotificationManager m_notificationManager; private int m_uniquePendingIntentId = 0; public NotifyByAndroid(android.content.Context context) { Log.i(TAG, context.getPackageName()); m_ctx = context; m_notificationManager = (NotificationManager)m_ctx.getSystemService(Context.NOTIFICATION_SERVICE); IntentFilter filter = new IntentFilter(); filter.addAction(m_ctx.getPackageName() + NOTIFICATION_ACTION); filter.addAction(m_ctx.getPackageName() + NOTIFICATION_DELETED); m_ctx.registerReceiver(this, filter); } public void notify(KNotification notification) { Log.i(TAG, notification.text); Notification.Builder builder = new Notification.Builder(m_ctx); - builder.setSmallIcon(notification.icon); + if (Build.VERSION.SDK_INT >= 23) { + builder.setSmallIcon((Icon)notification.icon); + } builder.setContentTitle(notification.title); builder.setContentText(notification.text); // taping the notification shows the app Intent intent = new Intent(m_ctx, m_ctx.getClass()); PendingIntent contentIntent = PendingIntent.getActivity(m_ctx, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(contentIntent); // actions int actionId = 0; for (String actionName : notification.actions) { Intent actionIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_ACTION); actionIntent.putExtra(NOTIFICATION_ID_EXTRA, notification.id); actionIntent.putExtra(NOTIFICATION_ACTION_ID_EXTRA, actionId); PendingIntent pendingIntent = PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); Notification.Action action = new Notification.Action.Builder(0, actionName, pendingIntent).build(); builder.addAction(action); ++actionId; } // notfication about user closing the notification Intent deleteIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_DELETED); deleteIntent.putExtra(NOTIFICATION_ID_EXTRA, notification.id); Log.i(TAG, deleteIntent.getExtras() + " " + notification.id); builder.setDeleteIntent(PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)); m_notificationManager.notify(notification.id, builder.build()); } public void close(int id) { m_notificationManager.cancel(id); } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); int id = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1); Log.i(TAG, action + ": " + id + " " + intent.getExtras()); if (action.equals(m_ctx.getPackageName() + NOTIFICATION_ACTION)) { int actionId = intent.getIntExtra(NOTIFICATION_ACTION_ID_EXTRA, -1); notificationActionInvoked(id, actionId); } else if (action.equals(m_ctx.getPackageName() + NOTIFICATION_DELETED)) { notificationFinished(id); } } public native void notificationFinished(int notificationId); public native void notificationActionInvoked(int notificationId, int action); } diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp index 2ef6e4a..53f95c9 100644 --- a/src/knotificationmanager.cpp +++ b/src/knotificationmanager.cpp @@ -1,352 +1,352 @@ /* This file is part of the KDE libraries Copyright (C) 2005 Olivier Goffart Copyright (C) 2013-2015 Martin Klapetek Copyright (C) 2017 Eike Hein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "knotificationmanager_p.h" #include "knotification.h" #include #include #include #include #include #include #include #ifdef QT_DBUS_LIB #include #include #endif #include "knotifyconfig.h" #include "knotificationplugin.h" #include "notifybylogfile.h" #include "notifybytaskbar.h" #include "notifybyexecute.h" #ifndef Q_OS_ANDROID #include "notifybypopup.h" #include "notifybyportal.h" #else #include "notifybyandroid.h" #endif #include "debug_p.h" #if defined(HAVE_CANBERRA) #include "notifybyaudio_canberra.h" #elif defined(HAVE_PHONON4QT5) #include "notifybyaudio_phonon.h" #endif #ifdef HAVE_SPEECH #include "notifybytts.h" #endif typedef QHash Dict; struct Q_DECL_HIDDEN KNotificationManager::Private { QHash notifications; QHash notifyPlugins; // incremental ids for notifications int notifyIdCounter; QStringList dirtyConfigCache; bool inSandbox = false; bool portalDBusServiceExists = false; }; class KNotificationManagerSingleton { public: KNotificationManager instance; }; Q_GLOBAL_STATIC(KNotificationManagerSingleton, s_self) KNotificationManager *KNotificationManager::self() { return &s_self()->instance; } KNotificationManager::KNotificationManager() : d(new Private) { d->notifyIdCounter = 0; qDeleteAll(d->notifyPlugins); d->notifyPlugins.clear(); if (!qEnvironmentVariableIsEmpty("XDG_RUNTIME_DIR")) { const QByteArray runtimeDir = qgetenv("XDG_RUNTIME_DIR"); if (!runtimeDir.isEmpty()) { d->inSandbox = QFileInfo::exists(QFile::decodeName(runtimeDir) + QLatin1String("/flatpak-info")); } } else if (qEnvironmentVariableIsSet("SNAP")) { d->inSandbox = true; } #ifdef QT_DBUS_LIB if (d->inSandbox) { QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); d->portalDBusServiceExists = interface->isServiceRegistered(QStringLiteral("org.freedesktop.portal.Desktop")); } QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/Config"), QStringLiteral("org.kde.knotification"), QStringLiteral("reparseConfiguration"), this, SLOT(reparseConfiguration(QString))); #endif } KNotificationManager::~KNotificationManager() { delete d; } KNotificationPlugin *KNotificationManager::pluginForAction(const QString &action) { KNotificationPlugin *plugin = d->notifyPlugins.value(action); // We already loaded a plugin for this action. if (plugin) { return plugin; } auto addPlugin = [this](KNotificationPlugin *plugin) { d->notifyPlugins[plugin->optionName()] = plugin; connect(plugin, &KNotificationPlugin::finished, this, &KNotificationManager::notifyPluginFinished); connect(plugin, &KNotificationPlugin::actionInvoked, this, &KNotificationManager::notificationActivated); }; // Load plugin. // We have a series of built-ins up first, and fall back to trying // to instantiate an externally supplied plugin. if (action == QLatin1String("Popup")) { #ifndef Q_OS_ANDROID if (d->inSandbox && d->portalDBusServiceExists) { plugin = new NotifyByPortal(this); } else { plugin = new NotifyByPopup(this); } -#elif __ANDROID_API__ >= 23 +#else plugin = new NotifyByAndroid(this); #endif addPlugin(plugin); } else if (action == QLatin1String("Taskbar")) { plugin = new NotifyByTaskbar(this); addPlugin(plugin); } else if (action == QLatin1String("Sound")) { #if defined(HAVE_PHONON4QT5) || defined(HAVE_CANBERRA) plugin = new NotifyByAudio(this); addPlugin(plugin); #endif } else if (action == QLatin1String("Execute")) { plugin = new NotifyByExecute(this); addPlugin(plugin); } else if (action == QLatin1String("Logfile")) { plugin = new NotifyByLogfile(this); addPlugin(plugin); } else if (action == QLatin1String("TTS")) { #ifdef HAVE_SPEECH plugin = new NotifyByTTS(this); addPlugin(plugin); #endif } else { bool pluginFound = false; QList plugins = KPluginLoader::instantiatePlugins(QStringLiteral("knotification/notifyplugins"), [&action, &pluginFound](const KPluginMetaData &data) { // KPluginLoader::instantiatePlugins loops over the plugins it // found and calls this function to determine whether to // instantiate them. We use a `pluginFound` var outside the // lambda to break out of the loop once we got a match. // The reason we can't just use KPluginLoader::findPlugins, // loop over the meta data and instantiate only one plugin // is because the X-KDE-KNotification-OptionName field is // optional (see TODO note below) and the matching plugin // may be among the plugins which don't have it. if (pluginFound) { return false; } const QJsonObject &rawData = data.rawData(); // This field is new-ish and optional. If it's not set we always // instantiate the plugin, unless we already got a match. // TODO KF6: Require X-KDE-KNotification-OptionName be set and // reject plugins without it. if (rawData.contains(QStringLiteral("X-KDE-KNotification-OptionName"))) { if (rawData.value(QStringLiteral("X-KDE-KNotification-OptionName")) == action) { pluginFound = true; } else { return false; } } return true; }, this); Q_FOREACH (QObject *pluginObj, plugins) { KNotificationPlugin *notifyPlugin = qobject_cast(pluginObj); if (notifyPlugin) { // We try to avoid unnecessary instantiations (see above), but // when they happen keep the resulting plugins around. addPlugin(notifyPlugin); // Get ready to return the plugin we got asked for. if (notifyPlugin->optionName() == action) { plugin = notifyPlugin; } } else { // Not our/valid plugin, so delete the created object. pluginObj->deleteLater(); } } } return plugin; } void KNotificationManager::notifyPluginFinished(KNotification *notification) { if (!notification || !d->notifications.contains(notification->id())) { return; } notification->deref(); } void KNotificationManager::notificationActivated(int id, int action) { if (d->notifications.contains(id)) { qCDebug(LOG_KNOTIFICATIONS) << id << " " << action; KNotification *n = d->notifications[id]; n->activate(action); close(id); } } void KNotificationManager::notificationClosed() { KNotification *notification = qobject_cast(sender()); if (!notification) { return; } // We cannot do d->notifications.find(notification->id()); here because the // notification->id() is -1 or -2 at this point, so we need to look for value for (auto iter = d->notifications.begin(); iter != d->notifications.end(); ++iter) { if (iter.value() == notification) { d->notifications.erase(iter); break; } } } void KNotificationManager::close(int id, bool force) { if (force || d->notifications.contains(id)) { KNotification *n = d->notifications.value(id); qCDebug(LOG_KNOTIFICATIONS) << "Closing notification" << id; // Find plugins that are actually acting on this notification // call close() only on those, otherwise each KNotificationPlugin::close() // will call finish() which may close-and-delete the KNotification object // before it finishes calling close on all the other plugins. // For example: Action=Popup is a single actions but there is 5 loaded // plugins, calling close() on the second would already close-and-delete // the notification KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action")); Q_FOREACH (const QString &action, notifyActions.split(QLatin1Char('|'))) { if (!d->notifyPlugins.contains(action)) { qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action; continue; } d->notifyPlugins[action]->close(n); } } } int KNotificationManager::notify(KNotification *n) { KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); if (d->dirtyConfigCache.contains(n->appName())) { notifyConfig.reparseSingleConfiguration(n->appName()); d->dirtyConfigCache.removeOne(n->appName()); } const QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action")); if (notifyActions.isEmpty() || notifyActions == QLatin1String("None")) { // this will cause KNotification closing itself fast n->ref(); n->deref(); return -1; } d->notifications.insert(d->notifyIdCounter, n); Q_FOREACH (const QString &action, notifyActions.split(QLatin1Char('|'))) { KNotificationPlugin *notifyPlugin = pluginForAction(action); if (!notifyPlugin) { qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action; continue; } n->ref(); qCDebug(LOG_KNOTIFICATIONS) << "Calling notify on" << notifyPlugin->optionName(); notifyPlugin->notify(n, ¬ifyConfig); } connect(n, &KNotification::closed, this, &KNotificationManager::notificationClosed); return d->notifyIdCounter++; } void KNotificationManager::update(KNotification *n) { KNotifyConfig notifyConfig(n->appName(), n->contexts(), n->eventId()); Q_FOREACH (KNotificationPlugin *p, d->notifyPlugins) { p->update(n, ¬ifyConfig); } } void KNotificationManager::reemit(KNotification *n) { notify(n); } void KNotificationManager::reparseConfiguration(const QString &app) { if (!d->dirtyConfigCache.contains(app)) { d->dirtyConfigCache << app; } } #include "moc_knotificationmanager_p.cpp" diff --git a/src/notifybyandroid.cpp b/src/notifybyandroid.cpp index e73b18d..ea3055d 100644 --- a/src/notifybyandroid.cpp +++ b/src/notifybyandroid.cpp @@ -1,168 +1,160 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "notifybyandroid.h" #include "knotification.h" #include "debug_p.h" #include #include #include #include #include static NotifyByAndroid *s_instance = nullptr; static void notificationFinished(JNIEnv *env, jobject that, jint notificationId) { Q_UNUSED(env); Q_UNUSED(that); if (s_instance) { s_instance->notificationFinished(notificationId); } } static void notificationActionInvoked(JNIEnv *env, jobject that, jint id, jint action) { Q_UNUSED(env); Q_UNUSED(that); if (s_instance) { s_instance->notificationActionInvoked(id, action); } } static const JNINativeMethod methods[] = { {"notificationFinished", "(I)V", (void*)notificationFinished}, {"notificationActionInvoked", "(II)V", (void*)notificationActionInvoked} }; KNOTIFICATIONS_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void*) { static bool initialized = false; if (initialized) { return JNI_VERSION_1_4; } initialized = true; JNIEnv *env = nullptr; if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { qCWarning(LOG_KNOTIFICATIONS) << "Failed to get JNI environment."; return -1; } jclass cls = env->FindClass("org/kde/knotifications/NotifyByAndroid"); if (env->RegisterNatives(cls, methods, sizeof(methods) / sizeof(JNINativeMethod)) < 0) { qCWarning(LOG_KNOTIFICATIONS) << "Failed to register native functions."; return -1; } return JNI_VERSION_1_4; } NotifyByAndroid::NotifyByAndroid(QObject* parent) : KNotificationPlugin(parent) { s_instance = this; -#if __ANDROID_API__ >= 23 m_backend = QAndroidJniObject("org/kde/knotifications/NotifyByAndroid", "(Landroid/content/Context;)V", QtAndroid::androidContext().object()); -#endif } NotifyByAndroid::~NotifyByAndroid() { s_instance = nullptr; } QString NotifyByAndroid::optionName() { return QStringLiteral("Popup"); } void NotifyByAndroid::notify(KNotification *notification, KNotifyConfig *config) { // HACK work around that notification->id() is only populated after returning from here QMetaObject::invokeMethod(this, [this, notification, config](){ notifyDeferred(notification, config); }, Qt::QueuedConnection); } void NotifyByAndroid::notifyDeferred(KNotification* notification, const KNotifyConfig* config) { Q_UNUSED(config); -#if __ANDROID_API__ >= 23 QAndroidJniEnvironment env; QAndroidJniObject n("org/kde/knotifications/KNotification", "()V"); n.setField("id", notification->id()); n.setField("text", QAndroidJniObject::fromString(notification->text()).object()); n.setField("title", QAndroidJniObject::fromString(notification->title()).object()); // icon QPixmap pixmap; if (!notification->iconName().isEmpty()) { const auto icon = QIcon::fromTheme(notification->iconName()); pixmap = icon.pixmap(32, 32); } else { pixmap = notification->pixmap(); } QByteArray iconData; QBuffer buffer(&iconData); buffer.open(QIODevice::WriteOnly); pixmap.save(&buffer, "PNG"); auto jIconData = env->NewByteArray(iconData.length()); env->SetByteArrayRegion(jIconData, 0, iconData.length(), reinterpret_cast(iconData.constData())); n.callMethod("setIconFromData", "([BI)V", jIconData, iconData.length()); // actions const auto actions = notification->actions(); for (const auto &action : actions) { n.callMethod("addAction", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(action).object()); } m_notifications.insert(notification->id(), notification); m_backend.callMethod("notify", "(Lorg/kde/knotifications/KNotification;)V", n.object()); -#else - Q_UNUSED(notification); -#endif } void NotifyByAndroid::close(KNotification* notification) { -#if __ANDROID_API__ >= 23 m_backend.callMethod("close", "(I)V", notification->id()); -#endif KNotificationPlugin::close(notification); } void NotifyByAndroid::notificationFinished(int id) { qCDebug(LOG_KNOTIFICATIONS) << id; const auto it = m_notifications.find(id); if (it == m_notifications.end()) { return; } m_notifications.erase(it); if (it.value()) { finish(it.value()); } } void NotifyByAndroid::notificationActionInvoked(int id, int action) { qCDebug(LOG_KNOTIFICATIONS) << id << action; emit actionInvoked(id, action); }