File indexing completed on 2024-04-21 03:56:28

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "notifybyandroid.h"
0008 #include "debug_p.h"
0009 #include "knotification.h"
0010 #include "knotificationreplyaction.h"
0011 #include "knotifyconfig.h"
0012 
0013 #include <QCoreApplication>
0014 #include <QJniEnvironment>
0015 
0016 #include <QBuffer>
0017 #include <QIcon>
0018 
0019 static NotifyByAndroid *s_instance = nullptr;
0020 
0021 static void notificationFinished(JNIEnv *env, jobject that, jint notificationId)
0022 {
0023     Q_UNUSED(env);
0024     Q_UNUSED(that);
0025     if (s_instance) {
0026         s_instance->notificationFinished(notificationId);
0027     }
0028 }
0029 
0030 static void notificationActionInvoked(JNIEnv *env, jobject that, jint id, jstring action)
0031 {
0032     Q_UNUSED(env);
0033     Q_UNUSED(that);
0034     if (s_instance) {
0035         const char *str = env->GetStringUTFChars(action, nullptr);
0036         s_instance->notificationActionInvoked(id, QString::fromUtf8(str));
0037         env->ReleaseStringUTFChars(action, str);
0038     }
0039 }
0040 
0041 static void notificationInlineReply(JNIEnv *env, jobject that, jint id, jstring text)
0042 {
0043     Q_UNUSED(that);
0044     if (s_instance) {
0045         const char *str = env->GetStringUTFChars(text, nullptr);
0046         s_instance->notificationInlineReply(id, QString::fromUtf8(str));
0047         env->ReleaseStringUTFChars(text, str);
0048     }
0049 }
0050 
0051 static const JNINativeMethod methods[] = {
0052     {"notificationFinished", "(I)V", (void *)notificationFinished},
0053     {"notificationActionInvoked", "(ILjava/lang/String;)V", (void *)notificationActionInvoked},
0054     {"notificationInlineReply", "(ILjava/lang/String;)V", (void *)notificationInlineReply},
0055 };
0056 
0057 KNOTIFICATIONS_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
0058 {
0059     static bool initialized = false;
0060     if (initialized) {
0061         return JNI_VERSION_1_4;
0062     }
0063     initialized = true;
0064 
0065     JNIEnv *env = nullptr;
0066     if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
0067         qCWarning(LOG_KNOTIFICATIONS) << "Failed to get JNI environment.";
0068         return -1;
0069     }
0070     jclass cls = env->FindClass("org/kde/knotifications/NotifyByAndroid");
0071     if (env->RegisterNatives(cls, methods, sizeof(methods) / sizeof(JNINativeMethod)) < 0) {
0072         qCWarning(LOG_KNOTIFICATIONS) << "Failed to register native functions.";
0073         return -1;
0074     }
0075 
0076     return JNI_VERSION_1_4;
0077 }
0078 
0079 NotifyByAndroid::NotifyByAndroid(QObject *parent)
0080     : KNotificationPlugin(parent)
0081 {
0082     s_instance = this;
0083     QJniObject context = QNativeInterface::QAndroidApplication::context();
0084     m_backend = QJniObject("org/kde/knotifications/NotifyByAndroid", "(Landroid/content/Context;)V", context.object<jobject>());
0085 }
0086 
0087 NotifyByAndroid::~NotifyByAndroid()
0088 {
0089     s_instance = nullptr;
0090 }
0091 
0092 QString NotifyByAndroid::optionName()
0093 {
0094     return QStringLiteral("Popup");
0095 }
0096 
0097 void NotifyByAndroid::notify(KNotification *notification, const KNotifyConfig &notifyConfig)
0098 {
0099     Q_UNUSED(notifyConfig);
0100     // HACK work around that notification->id() is only populated after returning from here
0101     // note that config will be invalid at that point, so we can't pass that along
0102     QMetaObject::invokeMethod(
0103         this,
0104         [this, notification]() {
0105             notifyDeferred(notification);
0106         },
0107         Qt::QueuedConnection);
0108 }
0109 
0110 QJniObject NotifyByAndroid::createAndroidNotification(KNotification *notification, const KNotifyConfig &notifyConfig) const
0111 {
0112     QJniEnvironment env;
0113     QJniObject n("org/kde/knotifications/KNotification", "()V");
0114     n.setField("id", notification->id());
0115     n.setField("text", QJniObject::fromString(stripRichText(notification->text())).object<jstring>());
0116     n.setField("richText", QJniObject::fromString(notification->text()).object<jstring>());
0117     n.setField("title", QJniObject::fromString(notification->title()).object<jstring>());
0118     n.setField("urgency", (jint)(notification->urgency() == KNotification::DefaultUrgency ? KNotification::HighUrgency : notification->urgency()));
0119     n.setField("visibility", QJniObject::fromString(notification->hints().value(QLatin1String("x-kde-visibility")).toString().toLower()).object<jstring>());
0120 
0121     n.setField("channelId", QJniObject::fromString(notification->eventId()).object<jstring>());
0122     n.setField("channelName", QJniObject::fromString(notifyConfig.readEntry(QLatin1String("Name"))).object<jstring>());
0123     n.setField("channelDescription", QJniObject::fromString(notifyConfig.readEntry(QLatin1String("Comment"))).object<jstring>());
0124 
0125     if ((notification->flags() & KNotification::SkipGrouping) == 0) {
0126         n.setField("group", QJniObject::fromString(notification->eventId()).object<jstring>());
0127     }
0128 
0129     // icon
0130     QPixmap pixmap;
0131     if (!notification->iconName().isEmpty()) {
0132         const auto icon = QIcon::fromTheme(notification->iconName());
0133         pixmap = icon.pixmap(32, 32);
0134     } else {
0135         pixmap = notification->pixmap();
0136     }
0137     QByteArray iconData;
0138     QBuffer buffer(&iconData);
0139     buffer.open(QIODevice::WriteOnly);
0140     pixmap.save(&buffer, "PNG");
0141     auto jIconData = env->NewByteArray(iconData.length());
0142     env->SetByteArrayRegion(jIconData, 0, iconData.length(), reinterpret_cast<const jbyte *>(iconData.constData()));
0143     n.callMethod<void>("setIconFromData", "([BI)V", jIconData, iconData.length());
0144     env->DeleteLocalRef(jIconData);
0145 
0146     // actions
0147     const auto actions = notification->actions();
0148     for (const KNotificationAction *action : actions) {
0149         n.callMethod<void>("addAction",
0150                            "(Ljava/lang/String;Ljava/lang/String;)V",
0151                            QJniObject::fromString(action->id()).object<jstring>(),
0152                            QJniObject::fromString(action->label()).object<jstring>());
0153     }
0154 
0155     if (notification->replyAction()) {
0156         n.setField("inlineReplyLabel", QJniObject::fromString(notification->replyAction()->label()).object<jstring>());
0157         n.setField("inlineReplyPlaceholder", QJniObject::fromString(notification->replyAction()->placeholderText()).object<jstring>());
0158     }
0159 
0160     return n;
0161 }
0162 
0163 void NotifyByAndroid::notifyDeferred(KNotification *notification)
0164 {
0165     KNotifyConfig config(notification->appName(), notification->eventId());
0166     const auto n = createAndroidNotification(notification, config);
0167     m_notifications.insert(notification->id(), notification);
0168 
0169     m_backend.callMethod<void>("notify", "(Lorg/kde/knotifications/KNotification;)V", n.object<jobject>());
0170 }
0171 
0172 void NotifyByAndroid::update(KNotification *notification, const KNotifyConfig &notifyConfig)
0173 {
0174     const auto n = createAndroidNotification(notification, notifyConfig);
0175     m_backend.callMethod<void>("notify", "(Lorg/kde/knotifications/KNotification;)V", n.object<jobject>());
0176 }
0177 
0178 void NotifyByAndroid::close(KNotification *notification)
0179 {
0180     m_backend.callMethod<void>("close", "(ILjava/lang/String;)V", notification->id(), QJniObject::fromString(notification->eventId()).object<jstring>());
0181     KNotificationPlugin::close(notification);
0182 }
0183 
0184 void NotifyByAndroid::notificationFinished(int id)
0185 {
0186     qCDebug(LOG_KNOTIFICATIONS) << id;
0187     const auto it = m_notifications.find(id);
0188     if (it == m_notifications.end()) {
0189         return;
0190     }
0191     m_notifications.erase(it);
0192     if (it.value()) {
0193         finish(it.value());
0194     }
0195 }
0196 
0197 void NotifyByAndroid::notificationActionInvoked(int id, const QString &action)
0198 {
0199     qCDebug(LOG_KNOTIFICATIONS) << id << action;
0200     Q_EMIT actionInvoked(id, action);
0201 }
0202 
0203 void NotifyByAndroid::notificationInlineReply(int id, const QString &text)
0204 {
0205     qCDebug(LOG_KNOTIFICATIONS) << id << text;
0206     Q_EMIT replied(id, text);
0207 }
0208 
0209 #include "moc_notifybyandroid.cpp"