File indexing completed on 2024-04-28 15:29:12
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"