v0.9.3: notification_send posts on the phone, and Java helpers have a home
The tool was the last Android one that never worked on the NX789J: it would fail with "Notification.Builder.build() failed: no exception detail" and the shade stayed empty. The detail was the whole story -- Qt clears a Java exception before C++ gets to look at it, so a build() that throws and a build() that returns null look exactly the same from here, and the only way to catch the difference is to let Java catch it. - app/android/src/xyz/whcself/cstagent/Notifier.java: the builder, the PendingIntent, build() and notify() in one try/catch that returns "" or the throwable's class and message. androiddeployqt copies app/android/ with the rest of the package source and Qt's Gradle template puts its src/ on java.srcDirs, so a class dropped there needs no build-script change (the first one is this; the foreground service of the next step goes there too). - AndroidTools.cpp keeps what it can see from C++ (the context, the channel on API 26+, the small icon looked up by name, the launch intent) and calls Notifier.post(); the result line now carries the Java failure when there is one. The javaErrorText() helper of the previous attempt is gone: reading a pending exception from C++ is exactly what does not work here. - On the device: the tool answers "notification sent", the system holds the record (channel cst-agent, importance 3, the framework small icon, the tap intent back into the app). Earlier attempts in the same session are in the transcript next to it: four failures from the old path, then this one. Docs: docs/android.md gets the Java-helper mechanism (and why the tool has to be wrapped in Java at all) and no longer lists notification_send as missing; app/README.md's platform table and the Android note say where a Java class goes. Version 0.9.3.
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
* unregisterPlatformTools() drops again.
|
||||
*
|
||||
* WHERE THE CODE RUNS: `run_ex` is called on the turn's worker thread, never
|
||||
* on the GUI thread. JNI calls are fine from any thread (QJniEnvironment
|
||||
* attaches it), Qt GUI objects are not -- a tool that needs the widget tree
|
||||
* on the GUI thread. JNI calls are fine from any thread (Qt attaches it),
|
||||
* Qt GUI objects are not -- a tool that needs the widget tree
|
||||
* has to hop back with a blocking queued call (onGuiThread() below).
|
||||
*
|
||||
* The clipboard tools keep the names of the engine's built-ins: those are
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <QFileInfo>
|
||||
#include <QFuture>
|
||||
#include <QGuiApplication>
|
||||
#include <QJniEnvironment>
|
||||
#include <QJniObject>
|
||||
#include <QMetaObject>
|
||||
#include <QString>
|
||||
@@ -101,39 +100,6 @@ int notifyProbe(char *why, size_t why_sz) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The message of the Java exception that is pending right now, if any. Qt
|
||||
* clears exceptions on the way out of a call, and build() throws rather than
|
||||
* returning: without this the tool could only say "it failed". */
|
||||
QString javaErrorText() {
|
||||
QJniEnvironment env;
|
||||
JNIEnv *e = env.jniEnv();
|
||||
if (!e || !e->ExceptionCheck())
|
||||
return QString();
|
||||
jthrowable th = e->ExceptionOccurred();
|
||||
e->ExceptionClear();
|
||||
QString text;
|
||||
if (th) {
|
||||
jclass cls = e->GetObjectClass(th);
|
||||
if (cls) {
|
||||
jmethodID mid = e->GetMethodID(cls, "getMessage", "()Ljava/lang/String;");
|
||||
if (mid) {
|
||||
jstring js = static_cast<jstring>(e->CallObjectMethod(th, mid));
|
||||
if (js) {
|
||||
const char *utf = e->GetStringUTFChars(js, nullptr);
|
||||
if (utf) {
|
||||
text = QString::fromUtf8(utf);
|
||||
e->ReleaseStringUTFChars(js, utf);
|
||||
}
|
||||
e->DeleteLocalRef(js);
|
||||
}
|
||||
}
|
||||
e->DeleteLocalRef(cls);
|
||||
}
|
||||
e->DeleteLocalRef(th);
|
||||
}
|
||||
return text.isEmpty() ? QStringLiteral("a Java exception was thrown") : text;
|
||||
}
|
||||
|
||||
bool postNotification(const QString &title, const QString &message,
|
||||
QString *err) {
|
||||
QJniObject ctx = appContext();
|
||||
@@ -150,7 +116,6 @@ bool postNotification(const QString &title, const QString &message,
|
||||
}
|
||||
|
||||
const QString channelId = QStringLiteral("cst-agent");
|
||||
QJniObject builder;
|
||||
if (sdkInt() >= 26) {
|
||||
QJniObject channel(
|
||||
"android/app/NotificationChannel",
|
||||
@@ -161,17 +126,6 @@ bool postNotification(const QString &title, const QString &message,
|
||||
manager.callMethod<void>("createNotificationChannel",
|
||||
"(Landroid/app/NotificationChannel;)V",
|
||||
channel.object<jobject>());
|
||||
builder = QJniObject("android/app/Notification$Builder",
|
||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||
ctx.object<jobject>(),
|
||||
QJniObject::fromString(channelId).object<jstring>());
|
||||
} else {
|
||||
builder = QJniObject("android/app/Notification$Builder",
|
||||
"(Landroid/content/Context;)V", ctx.object<jobject>());
|
||||
}
|
||||
if (!builder.isValid()) {
|
||||
*err = QStringLiteral("no Notification.Builder");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The small icon. The R classes are not reachable from here, so the id is
|
||||
@@ -215,54 +169,34 @@ bool postNotification(const QString &title, const QString &message,
|
||||
"resources nor the APK's own");
|
||||
return false;
|
||||
}
|
||||
builder.callObjectMethod(
|
||||
"setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;",
|
||||
QJniObject::fromString(title).object<jstring>());
|
||||
builder.callObjectMethod(
|
||||
"setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;",
|
||||
QJniObject::fromString(message).object<jstring>());
|
||||
builder.callObjectMethod(
|
||||
"setSmallIcon", "(I)Landroid/app/Notification$Builder;", icon);
|
||||
|
||||
/* Tapping it opens the app again (the launch intent of the package). */
|
||||
QJniObject pm = ctx.callObjectMethod(
|
||||
"getPackageManager", "()Landroid/content/pm/PackageManager;");
|
||||
if (pm.isValid() && pkg.isValid()) {
|
||||
QJniObject launch = pm.callObjectMethod(
|
||||
/* The builder, the build and the notify happen in Java (Notifier.java):
|
||||
* a Java exception is invisible to C++ once Qt's JNI wrapper has cleared
|
||||
* it, so on the phone this only ever said "failed, no exception detail"
|
||||
* while the shade stayed empty. Java catches it and hands back the line to
|
||||
* show. */
|
||||
QJniObject launch;
|
||||
if (pm.isValid() && pkg.isValid())
|
||||
launch = pm.callObjectMethod(
|
||||
"getLaunchIntentForPackage",
|
||||
"(Ljava/lang/String;)Landroid/content/Intent;", pkg.object<jstring>());
|
||||
if (launch.isValid()) {
|
||||
/* FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP */
|
||||
launch.callObjectMethod("addFlags", "(I)Landroid/content/Intent;",
|
||||
static_cast<jint>(0x10000000 | 0x04000000));
|
||||
QJniObject pending(
|
||||
"android/app/PendingIntent",
|
||||
"(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;",
|
||||
ctx.object<jobject>(), static_cast<jint>(0), launch.object<jobject>(),
|
||||
/* FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT */
|
||||
static_cast<jint>(0x04000000 | 0x08000000));
|
||||
if (pending.isValid())
|
||||
builder.callObjectMethod(
|
||||
"setContentIntent",
|
||||
"(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;",
|
||||
pending.object<jobject>());
|
||||
}
|
||||
}
|
||||
|
||||
QJniObject note = builder.callObjectMethod("build",
|
||||
"()Landroid/app/Notification;");
|
||||
if (!note.isValid()) {
|
||||
const QString java = javaErrorText();
|
||||
*err = QStringLiteral("Notification.Builder.build() failed: %1")
|
||||
.arg(java.isEmpty() ? QStringLiteral("no exception detail")
|
||||
: java);
|
||||
return false;
|
||||
}
|
||||
manager.callMethod<void>("notify", "(ILandroid/app/Notification;)V",
|
||||
static_cast<jint>(1), note.object<jobject>());
|
||||
const QString after = javaErrorText();
|
||||
if (!after.isEmpty()) {
|
||||
*err = QStringLiteral("NotificationManager.notify() failed: %1").arg(after);
|
||||
QJniObject posted = QJniObject::callStaticObjectMethod(
|
||||
"xyz/whcself/cstagent/Notifier", "post",
|
||||
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;"
|
||||
"Ljava/lang/String;ILandroid/content/Intent;I)Ljava/lang/String;",
|
||||
ctx.object<jobject>(),
|
||||
QJniObject::fromString(channelId).object<jstring>(),
|
||||
QJniObject::fromString(title).object<jstring>(),
|
||||
QJniObject::fromString(message).object<jstring>(), icon,
|
||||
launch.isValid() ? launch.object<jobject>() : nullptr,
|
||||
static_cast<jint>(1));
|
||||
const QString java =
|
||||
posted.isValid() ? posted.toString()
|
||||
: QStringLiteral("the Notifier helper is not in this APK");
|
||||
if (!java.isEmpty()) {
|
||||
*err = QStringLiteral("cannot post the notification: %1").arg(java);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user