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 ¬ifyConfig) 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 ¬ifyConfig) 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 ¬ifyConfig) 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"