File indexing completed on 2024-04-28 11:43:47

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