messenger-android

Android graphical user interfaces for GNUnet Messenger
Log | Files | Refs | README | LICENSE

commit 9eb549294279c8eebded56cc94892dd364c110eb
parent 51e33eccd6f0ef4bb8d87140e5755925369d4296
Author: t3sserakt <t3ss@posteo.de>
Date:   Wed, 13 May 2026 11:17:08 +0200

WIP: server crash for group, saving client from being crashed

Diffstat:
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt | 41+++++++++++++++++++++++++++++++++++------
MGNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt | 28++++++++++++++++++++++------
MGNUnetMessenger/app/src/main/res/values/strings.xml | 1+
3 files changed, 58 insertions(+), 12 deletions(-)

diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/service/boundimpl/GnunetChatBoundService.kt @@ -138,6 +138,35 @@ class GnunetChatBoundService( return appContext.bindService(intent, conn, Context.BIND_AUTO_CREATE) } + /** True if the AIDL binder is currently connected. UI can use this to + * short-circuit before issuing a sync call that would otherwise crash + * the main thread when the server has died. */ + fun isRemoteAlive(): Boolean = remoteRef.get() != null + + /** Wraps a `runBlocking { withReadyRemote(...) }` body and converts the + * IllegalStateException / DeadObjectException-wrapped RuntimeException + * that fires when the server is gone into a logged warning + the + * caller-supplied default. Use only for methods whose callers can + * tolerate a stale answer (isGroup → false, getGroupFromContext → null, + * etc). For methods whose callers can't, let the throw propagate and + * the UI layer is expected to wrap them. */ + private inline fun <T> safeSync(default: T, label: String, block: () -> T): T = try { + block() + } catch (e: IllegalStateException) { + Log.w(TAG, "$label: remote unavailable, returning default", e) + default + } catch (e: RuntimeException) { + val cause = e.cause + if (cause is IllegalStateException || cause is DeadObjectException || + cause is RemoteException + ) { + Log.w(TAG, "$label: remote call failed, returning default", e) + default + } else { + throw e + } + } + fun unbind() { val remote = remoteRef.get() val dr = deathRecipient @@ -569,8 +598,8 @@ class GnunetChatBoundService( } } - override fun getGroupFromContext(context: ChatContext): ChatGroup? { - return runBlocking { + override fun getGroupFromContext(context: ChatContext): ChatGroup? = safeSync(null, "getGroupFromContext") { + runBlocking { val groupDto = withReadyRemote(lastHandle) { remote, _ -> remote.getGroupFromContext(context.toDto()) } @@ -848,16 +877,16 @@ class GnunetChatBoundService( } } - override fun isGroup(context: ChatContext): Boolean { - return runBlocking { + override fun isGroup(context: ChatContext): Boolean = safeSync(false, "isGroup") { + runBlocking { withReadyRemote(lastHandle) { remote, _ -> remote.isGroup(context.toDto()) } } } - override fun isPlatform(context: ChatContext): Boolean { - return runBlocking { + override fun isPlatform(context: ChatContext): Boolean = safeSync(false, "isPlatform") { + runBlocking { withReadyRemote(lastHandle) { remote, _ -> remote.isPlatform(context.toDto()) } diff --git a/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt b/GNUnetMessenger/app/src/main/java/org/gnunet/gnunetmessenger/ui/chat/ChatFragment.kt @@ -34,6 +34,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.EditText +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.MenuHost import androidx.core.view.MenuProvider @@ -151,13 +152,28 @@ class ChatFragment : Fragment(R.layout.fragment_chat) { } } }, viewLifecycleOwner) - val title = when { - gnunetChat.isGroup(chatContext) || gnunetChat.isPlatform(chatContext) -> { - gnunetChat.getGroupFromContext(chatContext)?.name ?: getString(R.string.placeholder_label_chat) - } - else -> { - gnunetChat.getContextContact(chatContext).name + val title = try { + when { + gnunetChat.isGroup(chatContext) || gnunetChat.isPlatform(chatContext) -> { + gnunetChat.getGroupFromContext(chatContext)?.name + ?: getString(R.string.placeholder_label_chat) + } + else -> { + gnunetChat.getContextContact(chatContext).name + } } + } catch (t: Throwable) { + // The server died (or the binder hasn't reattached yet). Bounce + // the user back to the chat list instead of letting the + // IllegalStateException kill the whole app on the main thread. + Log.w("ChatFragment", "Server unreachable while opening chat; popping back stack", t) + Toast.makeText( + requireContext(), + getString(R.string.toast_server_unreachable), + Toast.LENGTH_SHORT + ).show() + runCatching { findNavController().popBackStack() } + return } (requireActivity() as AppCompatActivity).supportActionBar?.title = title } diff --git a/GNUnetMessenger/app/src/main/res/values/strings.xml b/GNUnetMessenger/app/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ <string name="gnunet_connection_failed">Could not connect to GNUnet.</string> <string name="account_connect_failed">Could not activate the selected account.</string> <string name="account_list_load_failed">Could not load the account list.</string> + <string name="toast_server_unreachable">GNUnet server unreachable — please retry.</string> <string-array name="lobby_lifetimes"> <item>Off</item>