Skip to content

Commit

Permalink
Handle destruction of activity more gracefully
Browse files Browse the repository at this point in the history
Since Qt will put the app in a state where it can't recover from (the UI
can never be shown again) we have to terminate everything else as well.
Otherwise Go threads and the service/notification would still be around but
one could never access the UI again.

This is of course not ideal. Ideally, the activity lifecycle should be
independent from the service livecycle and the service should be able to
outlive the activity. The activity should be able to re-create itself once
destroyed.
  • Loading branch information
Martchus committed Jan 11, 2025
1 parent 612dc36 commit b37dd81
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 23 deletions.
11 changes: 9 additions & 2 deletions tray/android/src/io/github/martchus/syncthingtray/Activity.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,13 @@ public void onStop() {
}

public void onDestroy() {
// stop service and libsyncthing
// note: QtActivity will exit the main thread in super.onDestroy() so we cannot keep the service running.
// It would not stop the service and Go threads but it would be impossible to re-enter the UI leaving
// the app in some kind of zombie state.
Log.i(TAG, "Destroying");
stopLibSyncthing();
stopSyncthingService();
super.onDestroy();
}

Expand All @@ -161,11 +167,12 @@ protected void onNewIntent(Intent intent) {

private void sendAndroidIntentToQtQuickApp(String page, boolean fromNotification) {
try {
onAndroidIntent(page, fromNotification);
handleAndroidIntent(page, fromNotification);
} catch (java.lang.UnsatisfiedLinkError e) {
showToast("Unable to open in Syncthing Tray app right now.");
}
}

private static native void onAndroidIntent(String page, boolean fromNotification);
private static native void handleAndroidIntent(String page, boolean fromNotification);
private static native void stopLibSyncthing();
}
53 changes: 33 additions & 20 deletions tray/gui/quick/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,21 @@ static void deletePipelineCache()
}

#ifdef Q_OS_ANDROID
// define functions called from Java
/// \brief The JniFn namespace defines functions called from Java.
namespace JniFn {
static App *appObjectForJava = nullptr;

static void onAndroidIntent(JNIEnv *, jobject, jstring page, jboolean fromNotification)
static void stopLibSyncthing(JNIEnv *, jobject)
{
QMetaObject::invokeMethod(appObjectForJava, "stopLibSyncthing", Qt::QueuedConnection);
}

static void handleAndroidIntent(JNIEnv *, jobject, jstring page, jboolean fromNotification)
{
QMetaObject::invokeMethod(appObjectForJava, "handleAndroidIntent", Qt::QueuedConnection,
Q_ARG(QString, QJniObject::fromLocalRef(page).toString()), Q_ARG(bool, fromNotification));
}
}
#endif

App::App(bool insecure, QObject *parent)
Expand Down Expand Up @@ -178,14 +185,15 @@ App::App(bool insecure, QObject *parent)

#ifdef Q_OS_ANDROID
// register native methods of Android activity
if (!appObjectForJava) {
appObjectForJava = this;
if (!JniFn::appObjectForJava) {
JniFn::appObjectForJava = this;
auto env = QJniEnvironment();
auto registeredMethods = true;
static const JNINativeMethod activityMethods[] = {
{ "onAndroidIntent", "(Ljava/lang/String;Z)V", reinterpret_cast<void *>(onAndroidIntent) },
{ "handleAndroidIntent", "(Ljava/lang/String;Z)V", reinterpret_cast<void *>(JniFn::handleAndroidIntent) },
{ "stopLibSyncthing", "()V", reinterpret_cast<void *>(JniFn::stopLibSyncthing) },
};
registeredMethods = env.registerNativeMethods("io/github/martchus/syncthingtray/Activity", activityMethods, 1) && registeredMethods;
registeredMethods = env.registerNativeMethods("io/github/martchus/syncthingtray/Activity", activityMethods, 2) && registeredMethods;
if (!registeredMethods) {
qWarning() << "Unable to register all native methods in JNI environment.";
}
Expand Down Expand Up @@ -695,6 +703,11 @@ void App::handleNewErrors(const std::vector<Data::SyncthingError> &errors)
{
Q_UNUSED(errors)
invalidateStatus();
#ifdef Q_OS_ANDROID
if (errors.empty()) {
clearSyncthingErrorsNotification();
}
#endif
}

void App::handleStateChanged(Qt::ApplicationState state)
Expand Down Expand Up @@ -766,27 +779,22 @@ void App::clearAndroidExtraNotifications(int firstId, int lastId)

void App::updateSyncthingErrorsNotification(CppUtilities::DateTime when, const QString &message)
{
auto whenString = when.toString();
m_syncthingErrors.reserve(m_syncthingErrors.size() + 1 + whenString.size() + 2 + message.size());
if (!m_syncthingErrors.isEmpty()) {
m_syncthingErrors += QChar('\n');
}
m_syncthingErrors += std::move(whenString);
m_syncthingErrors += QChar(':');
m_syncthingErrors += QChar(' ');
m_syncthingErrors += message;

const auto title = QJniObject::fromString(tr("Syncthing errors/notifications"));
static const auto text = QJniObject::fromString(QString());
const auto subText = QJniObject::fromString(m_syncthingErrors);
++m_syncthingErrors;
const auto title = QJniObject::fromString(m_syncthingErrors == 1
? tr("Syncthing error/notification")
: tr("%1 Syncthing errors/notifications").arg(m_syncthingErrors));
const auto text = QJniObject::fromString(m_syncthingErrors == 1
? message
: tr("most recent: ") + message);
const auto subText = QJniObject::fromString(QString::fromStdString(when.toString()));
static const auto page = QJniObject::fromString(QStringLiteral("connectionErrors"));
const auto &icon = makeAndroidIcon(commonForkAwesomeIcons().exclamation);
updateExtraAndroidNotification(title, text, subText, page, icon, 2);
}

void App::clearSyncthingErrorsNotification()
{
m_syncthingErrors.clear();
m_syncthingErrors = 0;
clearAndroidExtraNotifications(2, -1);
}

Expand Down Expand Up @@ -857,6 +865,11 @@ void App::handleAndroidIntent(const QString &data, bool fromNotification)
emit newDirTriggered(folderRef.deviceId.toString(), folderRef.folderId.toString(), folderRef.folderLabel.toString());
}
}

void App::stopLibSyncthing()
{
m_launcher.stopLibSyncthing();
}
#endif

void App::clearInternalErrors()
Expand Down
3 changes: 2 additions & 1 deletion tray/gui/quick/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ private Q_SLOTS:
void showNewDevice(const QString &devId, const QString &message);
void showNewDir(const QString &devId, const QString &dirId, const QString &dirLabel, const QString &message);
void handleAndroidIntent(const QString &page, bool fromNotification);
void stopLibSyncthing();
#endif

private:
Expand All @@ -315,7 +316,7 @@ private Q_SLOTS:
QVariantList m_internalErrors;
StatusInfo m_statusInfo;
#ifdef Q_OS_ANDROID
QString m_syncthingErrors;
int m_syncthingErrors = 0;
QHash<const QIcon *, QJniObject> m_androidIconCache;
int m_androidNotificationId = 100000000;
#endif
Expand Down

0 comments on commit b37dd81

Please sign in to comment.