messenger-android

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

GnunetChatBoundService.kt (36631B)


      1 package org.gnunet.gnunetmessenger.service.boundimpl
      2 
      3 import android.content.ComponentName
      4 import android.content.Context
      5 import android.content.Intent
      6 import android.content.ServiceConnection
      7 import android.os.DeadObjectException
      8 import android.os.IBinder
      9 import android.os.RemoteException
     10 import android.util.Log
     11 import kotlinx.coroutines.CompletableDeferred
     12 import kotlinx.coroutines.CoroutineScope
     13 import kotlinx.coroutines.Dispatchers
     14 import kotlinx.coroutines.SupervisorJob
     15 import kotlinx.coroutines.delay
     16 import kotlinx.coroutines.launch
     17 import kotlinx.coroutines.runBlocking
     18 import kotlinx.coroutines.withContext
     19 import org.gnunet.gnunetmessenger.ipc.*
     20 import org.gnunet.gnunetmessenger.model.*
     21 import org.gnunet.gnunetmessenger.service.GnunetChat
     22 import java.util.concurrent.ConcurrentHashMap
     23 import java.util.concurrent.atomic.AtomicReference
     24 
     25 class GnunetChatBoundService(
     26     private val appContext: Context
     27 ) : GnunetChat {
     28 
     29     private var uuidCounter: Long = 0
     30 
     31     private val mainScope: CoroutineScope =
     32         CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
     33     private val ioScope: CoroutineScope =
     34         CoroutineScope(SupervisorJob() + Dispatchers.IO)
     35 
     36     private val remoteRef = AtomicReference<IGnunetChat?>()
     37     private var deathRecipient: IBinder.DeathRecipient? = null
     38 
     39     @Volatile
     40     private var lastHandle: ChatHandle = ChatHandle(0L)
     41 
     42     @Volatile
     43     private lateinit var messageCallback: ((ChatContext, ChatMessage) -> Unit)
     44 
     45     private val pendingAfterHandle = mutableListOf<(IGnunetChat, Long) -> Unit>()
     46 
     47     private val handleReady = ConcurrentHashMap<ChatHandle, CompletableDeferred<Long>>()
     48 
     49     private val binderCallback = object : IChatCallback.Stub() {
     50         override fun onMessage(context: ChatContextDto, message: ChatMessageDto) {
     51             try {
     52                 val ctxLocal = context.toLocal()
     53                 val msgLocal = message.toLocal(ctxLocal)
     54                 mainScope.launch { messageCallback.invoke(ctxLocal, msgLocal) }
     55             } catch (t: Throwable) {
     56                 Log.e(TAG, "onMessage mapping failed", t)
     57             }
     58         }
     59     }
     60 
     61     private val conn = object : ServiceConnection {
     62         override fun onServiceConnected(name: ComponentName, service: IBinder) {
     63             val remote = IGnunetChat.Stub.asInterface(service)
     64             remoteRef.set(remote)
     65 
     66             val dr = IBinder.DeathRecipient {
     67                 Log.w(TAG, "Remote binder died")
     68                 remoteRef.set(null)
     69                 lastHandle.pointer = 0L
     70                 deathRecipient = null
     71             }
     72             deathRecipient = dr
     73             runCatching { service.linkToDeath(dr, 0) }
     74                 .onFailure { Log.e(TAG, "linkToDeath failed", it) }
     75         }
     76 
     77         override fun onServiceDisconnected(name: ComponentName) {
     78             Log.w(TAG, "Remote disconnected")
     79             remoteRef.set(null)
     80             lastHandle.pointer = 0L
     81         }
     82     }
     83 
     84     init {
     85         bind()
     86     }
     87 
     88     private suspend fun getOrBindRemote(maxWaitMs: Long = 2_000): IGnunetChat {
     89         remoteRef.get()?.let { return it }
     90         bind()
     91         var waited = 0L
     92         while (waited < maxWaitMs) {
     93             remoteRef.get()?.let { return it }
     94             delay(100)
     95             waited += 100
     96         }
     97         throw IllegalStateException("Remote not connected (timeout)")
     98     }
     99 
    100     private suspend inline fun <T> withReadyRemote(
    101         handle: ChatHandle,
    102         retries: Int = 1,
    103         crossinline block: suspend (IGnunetChat, Long) -> T
    104     ): T {
    105         awaitReady(handle)
    106         var attempt = 0
    107         var lastError: Throwable? = null
    108         while (attempt <= retries) {
    109             val remote = try {
    110                 getOrBindRemote()
    111             } catch (t: Throwable) {
    112                 lastError = t
    113                 null
    114             }
    115 
    116             if (remote != null) {
    117                 try {
    118                     return block(remote, handle.pointer)
    119                 } catch (dead: DeadObjectException) {
    120                     Log.w(TAG, "Binder died, rebinding… (attempt=$attempt)")
    121                     bind()
    122                     lastError = dead
    123                 } catch (re: RemoteException) {
    124                     Log.w(TAG, "RemoteException, retry if possible (attempt=$attempt)", re)
    125                     bind()
    126                     lastError = re
    127                 }
    128             } else {
    129                 delay(150)
    130             }
    131             attempt++
    132         }
    133         throw RuntimeException("Remote call failed after retries", lastError)
    134     }
    135 
    136     private fun bind(): Boolean {
    137         val intent = Intent(ACTION_BIND_GNUNET_CHAT).setPackage(SERVER_PACKAGE)
    138         return appContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)
    139     }
    140 
    141     /** True if the AIDL binder is currently connected. UI can use this to
    142      *  short-circuit before issuing a sync call that would otherwise crash
    143      *  the main thread when the server has died. */
    144     fun isRemoteAlive(): Boolean = remoteRef.get() != null
    145 
    146     /** Wraps a `runBlocking { withReadyRemote(...) }` body and converts the
    147      *  IllegalStateException / DeadObjectException-wrapped RuntimeException
    148      *  that fires when the server is gone into a logged warning + the
    149      *  caller-supplied default. Use only for methods whose callers can
    150      *  tolerate a stale answer (isGroup → false, getGroupFromContext → null,
    151      *  etc). For methods whose callers can't, let the throw propagate and
    152      *  the UI layer is expected to wrap them. */
    153     private inline fun <T> safeSync(default: T, label: String, block: () -> T): T = try {
    154         block()
    155     } catch (e: IllegalStateException) {
    156         Log.w(TAG, "$label: remote unavailable, returning default", e)
    157         default
    158     } catch (e: RuntimeException) {
    159         val cause = e.cause
    160         if (cause is IllegalStateException || cause is DeadObjectException ||
    161             cause is RemoteException
    162         ) {
    163             Log.w(TAG, "$label: remote call failed, returning default", e)
    164             default
    165         } else {
    166             throw e
    167         }
    168     }
    169 
    170     fun unbind() {
    171         val remote = remoteRef.get()
    172         val dr = deathRecipient
    173         if (remote != null && dr != null) {
    174             runCatching { remote.asBinder().unlinkToDeath(dr, 0) }
    175                 .onFailure { Log.w(TAG, "unlinkToDeath failed", it) }
    176         }
    177         deathRecipient = null
    178         remoteRef.set(null)
    179         lastHandle.pointer = 0L
    180         runCatching { appContext.unbindService(conn) }
    181             .onFailure { Log.w(TAG, "unbindService failed", it) }
    182     }
    183 
    184     override suspend fun awaitReady(handle: ChatHandle) {
    185         if (handle.pointer != 0L) {
    186             if (remoteRef.get() != null) return
    187             Log.w(TAG, "awaitReady: remote lost, resetting stale handle=${handle.pointer}")
    188             handle.pointer = 0L
    189         }
    190 
    191         handleReady[handle]?.let { deferred ->
    192             try {
    193                 val h = deferred.await()
    194                 if (handle.pointer == 0L) {
    195                     handle.pointer = h
    196                 }
    197                 return
    198             } finally {
    199                 handleReady.remove(handle)
    200             }
    201         }
    202 
    203         if (remoteRef.get() == null) {
    204             bind()
    205         }
    206 
    207         repeat(20) {
    208             if (remoteRef.get() != null) return@repeat
    209             delay(100)
    210         }
    211 
    212         val remote = remoteRef.get()
    213             ?: throw IllegalStateException("Remote not connected; startChat/bind() not completed")
    214 
    215         if (handle.pointer == 0L) {
    216             val real = remote.startChat(DEFAULT_APP_NAME, binderCallback)
    217             handle.pointer = real
    218             lastHandle.pointer = real
    219             handleReady[handle]?.complete(real)
    220             handleReady.remove(handle)
    221             drainPending(remote, real)
    222         }
    223 
    224         check(handle.pointer != 0L) { "Handle not ready (pointer==0)" }
    225     }
    226 
    227     override fun startChat(
    228         messengerApp: MessengerApp,
    229         callback: (ChatContext, ChatMessage) -> Unit
    230     ): ChatHandle {
    231         messageCallback = callback
    232 
    233         val ch = ChatHandle(0L)
    234         val deferred = CompletableDeferred<Long>()
    235         handleReady[ch] = deferred
    236 
    237         ioScope.launch {
    238             try {
    239                 val remote = getOrBindRemote()
    240                 val h = remote.startChat("messengerApp", binderCallback)
    241                 lastHandle.pointer = h
    242                 ch.pointer = h
    243                 deferred.complete(h)
    244                 drainPending(remote, h)
    245                 Log.d(TAG, "startChat -> handle=$h")
    246             } catch (t: Throwable) {
    247                 Log.e(TAG, "startChat failed", t)
    248                 deferred.completeExceptionally(t)
    249             }
    250         }
    251 
    252         return ch
    253     }
    254 
    255     override suspend fun reset() = withContext(Dispatchers.IO) {
    256         val remote = getOrBindRemote()
    257         remote.reset()
    258         Log.i(TAG, "reset: successfully reset remote service")
    259 
    260         lastHandle.pointer = 0L
    261         handleReady.clear()
    262         synchronized(pendingAfterHandle) {
    263             pendingAfterHandle.clear()
    264         }
    265     }
    266 
    267     override fun iterateAccounts(handle: ChatHandle, callback: (ChatAccount) -> Unit) {
    268         val bridge = object : IAccountCallback.Stub() {
    269             override fun onAccount(accountDto: ChatAccountDto) {
    270                 val acc = accountDto.toLocal()
    271                 mainScope.launch { callback(acc) }
    272             }
    273 
    274             override fun onDone() {
    275                 Log.d(TAG, "iterateAccounts: done")
    276             }
    277 
    278             override fun onError(code: Int, message: String?) {
    279                 Log.e(TAG, "iterateAccounts: error $code $message")
    280             }
    281         }
    282 
    283         val remote = remoteRef.get()
    284         val h = lastHandle.takeIf { it.pointer != 0L } ?: handle
    285 
    286         when {
    287             remote != null && h.pointer != 0L -> {
    288                 ioScope.launch {
    289                     try {
    290                         remote.iterateAccounts(h.pointer, bridge)
    291                     } catch (dead: DeadObjectException) {
    292                         Log.w(TAG, "iterateAccounts: binder died, queue & rebind")
    293                         synchronized(pendingAfterHandle) {
    294                             pendingAfterHandle += { r, real ->
    295                                 runCatching { r.iterateAccounts(real, bridge) }
    296                                     .onFailure {
    297                                         Log.e(TAG, "iterateAccounts (deferred) failed", it)
    298                                     }
    299                             }
    300                         }
    301                         bind()
    302                     } catch (e: RemoteException) {
    303                         Log.e(TAG, "iterateAccounts remote failed", e)
    304                         synchronized(pendingAfterHandle) {
    305                             pendingAfterHandle += { r, real ->
    306                                 runCatching { r.iterateAccounts(real, bridge) }
    307                                     .onFailure {
    308                                         Log.e(TAG, "iterateAccounts (deferred) failed", it)
    309                                     }
    310                             }
    311                         }
    312                         bind()
    313                     }
    314                 }
    315             }
    316 
    317             remote != null && h.pointer == 0L -> {
    318                 synchronized(pendingAfterHandle) {
    319                     pendingAfterHandle += { r, real ->
    320                         runCatching { r.iterateAccounts(real, bridge) }
    321                             .onFailure { Log.e(TAG, "iterateAccounts (deferred) failed", it) }
    322                     }
    323                 }
    324             }
    325 
    326             else -> {
    327                 synchronized(pendingAfterHandle) {
    328                     pendingAfterHandle += { r, real ->
    329                         runCatching { r.iterateAccounts(real, bridge) }
    330                             .onFailure { Log.e(TAG, "iterateAccounts (deferred) failed", it) }
    331                     }
    332                 }
    333                 bind()
    334             }
    335         }
    336     }
    337 
    338     override suspend fun listAccounts(handle: ChatHandle): List<ChatAccount> {
    339         return withReadyRemote(handle) { remote, h ->
    340             val result = mutableListOf<ChatAccount>()
    341             val done = CompletableDeferred<Unit>()
    342 
    343             val bridge = object : IAccountCallback.Stub() {
    344                 override fun onAccount(accountDto: ChatAccountDto) {
    345                     result.add(accountDto.toLocal())
    346                 }
    347 
    348                 override fun onDone() {
    349                     if (!done.isCompleted) {
    350                         done.complete(Unit)
    351                     }
    352                 }
    353 
    354                 override fun onError(code: Int, message: String?) {
    355                     if (!done.isCompleted) {
    356                         done.completeExceptionally(
    357                             IllegalStateException("iterateAccounts failed: $code ${message ?: ""}".trim())
    358                         )
    359                     }
    360                 }
    361             }
    362 
    363             remote.iterateAccounts(h, bridge)
    364             done.await()
    365             result.toList()
    366         }
    367     }
    368 
    369     private fun drainPending(remote: IGnunetChat, handle: Long) {
    370         val tasks = synchronized(pendingAfterHandle) {
    371             if (pendingAfterHandle.isEmpty()) return
    372             val copy = pendingAfterHandle.toList()
    373             pendingAfterHandle.clear()
    374             copy
    375         }
    376         ioScope.launch {
    377             tasks.forEach { task ->
    378                 runCatching { task(remote, handle) }
    379                     .onFailure { Log.e(TAG, "deferred task failed", it) }
    380             }
    381         }
    382     }
    383 
    384     private fun Int.toGnunetReturn(): GnunetReturnValue =
    385         when (this) {
    386             0 -> GnunetReturnValue.OK
    387             else -> GnunetReturnValue.NO
    388         }
    389 
    390     override suspend fun createAccount(handle: ChatHandle, name: String): GnunetReturnValue {
    391         val code = withReadyRemote(handle) { remote, h ->
    392             withContext(Dispatchers.IO) { remote.createAccount(h, name) }
    393         }
    394         return when (code) {
    395             1 -> GnunetReturnValue.OK
    396             else -> GnunetReturnValue.NO
    397         }
    398     }
    399 
    400     override suspend fun connect(handle: ChatHandle, account: ChatAccount) {
    401         withReadyRemote(handle) { remote, h ->
    402             withContext(Dispatchers.IO) { remote.connect(h, account.toDto()) }
    403         }
    404     }
    405 
    406     override suspend fun disconnect(handle: ChatHandle) {
    407         withReadyRemote(handle) { remote, h ->
    408             withContext(Dispatchers.IO) { remote.disconnect(h) }
    409         }
    410     }
    411 
    412     override suspend fun stopChat(handle: ChatHandle) {
    413         withReadyRemote<Unit>(handle) { remote, h ->
    414             withContext(Dispatchers.IO) { remote.stopChat(h) }
    415         }
    416     }
    417 
    418     override suspend fun getProfileName(handle: ChatHandle): String {
    419         return withReadyRemote(handle) { remote, h ->
    420             withContext(Dispatchers.IO) { remote.getProfileName(h) ?: "" }
    421         }
    422     }
    423 
    424     override suspend fun setProfileName(handle: ChatHandle, name: String) {
    425         withReadyRemote(handle) { remote, h ->
    426             withContext(Dispatchers.IO) { remote.setProfileName(h, name) }
    427         }
    428     }
    429 
    430     override fun getProfileKey(handle: ChatHandle): String {
    431         return runBlocking {
    432             withReadyRemote(handle) { remote, h ->
    433                 remote.getProfileKey(h)
    434             }
    435         }
    436     }
    437 
    438     override fun isContactBlocked(contact: ChatContact): Boolean {
    439         return runBlocking {
    440             withReadyRemote(lastHandle) { remote, _ ->
    441                 remote.isContactBlocked(contact.toDto())
    442             }
    443         }
    444     }
    445 
    446     override fun setContactBlocked(contact: ChatContact, isBlocked: Boolean) {
    447         runBlocking {
    448             withReadyRemote(lastHandle) { remote, _ ->
    449                 remote.setContactBlocked(contact.toDto(), isBlocked)
    450             }
    451         }
    452     }
    453 
    454     override fun setAttribute(handle: ChatHandle, key: String, value: String) {
    455         runBlocking {
    456             withReadyRemote(handle) { remote, h ->
    457                 remote.setAttribute(h, key, value)
    458             }
    459         }
    460     }
    461 
    462     override fun getAttributes(handle: ChatHandle, callback: (String, String) -> Unit) {
    463         val bridge = object : IAttributeCallback.Stub() {
    464             override fun onAttribute(key: String, value: String) {
    465                 mainScope.launch { callback(key, value) }
    466             }
    467 
    468             override fun onDone() {
    469                 Log.d(TAG, "getAttributes: done")
    470             }
    471 
    472             override fun onError(code: Int, message: String?) {
    473                 Log.e(TAG, "getAttributes: error $code $message")
    474             }
    475         }
    476 
    477         val remote = remoteRef.get()
    478         if (remote != null) {
    479             ioScope.launch {
    480                 try {
    481                     remote.getAttributes(handle.pointer, bridge)
    482                 } catch (e: RemoteException) {
    483                     Log.e(TAG, "getAttributes failed", e)
    484                 }
    485             }
    486         } else {
    487             bind()
    488             synchronized(pendingAfterHandle) {
    489                 pendingAfterHandle += { r, real ->
    490                     runCatching { r.getAttributes(real, bridge) }
    491                         .onFailure { Log.e(TAG, "getAttributes (deferred) failed", it) }
    492                 }
    493             }
    494         }
    495     }
    496 
    497     override fun lobbyOpen(handle: ChatHandle, callback: (String) -> Unit) {
    498         val bridge = object : ILobbyCallback.Stub() {
    499             override fun onLobbyUri(uri: String) {
    500                 mainScope.launch { callback(uri) }
    501             }
    502 
    503             override fun onError(code: Int, message: String?) {
    504                 Log.e(TAG, "lobbyOpen: error $code $message")
    505             }
    506         }
    507 
    508         val remote = remoteRef.get()
    509         if (remote != null) {
    510             ioScope.launch {
    511                 try {
    512                     remote.lobbyOpen(handle.pointer, bridge)
    513                 } catch (e: RemoteException) {
    514                     Log.e(TAG, "lobbyOpen failed", e)
    515                 }
    516             }
    517         } else {
    518             bind()
    519             synchronized(pendingAfterHandle) {
    520                 pendingAfterHandle += { r, real ->
    521                     runCatching { r.lobbyOpen(real, bridge) }
    522                         .onFailure { Log.e(TAG, "lobbyOpen (deferred) failed", it) }
    523                 }
    524             }
    525         }
    526     }
    527 
    528     override suspend fun lobbyJoin(handle: ChatHandle, uri: String) {
    529         withReadyRemote(handle) { remote, h ->
    530             withContext(Dispatchers.IO) { remote.lobbyJoin(h, uri) }
    531         }
    532     }
    533 
    534     override fun setGroupName(group: ChatGroup, name: String) {
    535         runBlocking {
    536             withReadyRemote(lastHandle) { remote, _ ->
    537                 remote.setGroupName(group.toDto(), name)
    538             }
    539         }
    540     }
    541 
    542     override fun createGroup(handle: ChatHandle, topic: String): ChatGroup {
    543         return runBlocking {
    544             val groupDto = withReadyRemote(handle) { remote, h ->
    545                 remote.createGroup(h, topic)
    546             }
    547             groupDto.toLocal()
    548         }
    549     }
    550 
    551     override fun parseUri(uri: String): ChatUri {
    552         return runBlocking {
    553             val uriDto = withReadyRemote(lastHandle) { remote, _ ->
    554                 remote.parseUri(uri)
    555             }
    556             uriDto.toLocal()
    557         }
    558     }
    559 
    560     override fun destroyUri(uri: ChatUri) {
    561         runBlocking {
    562             withReadyRemote(lastHandle) { remote, _ ->
    563                 remote.destroyUri(uri.toDto())
    564             }
    565         }
    566     }
    567 
    568     override fun inviteContactToGroup(group: ChatGroup, contact: ChatContact) {
    569         runBlocking {
    570             withReadyRemote(lastHandle) { remote, _ ->
    571                 remote.inviteContactToGroup(group.toDto(), contact.toDto())
    572             }
    573         }
    574     }
    575 
    576     override fun getUserPointerForContext(context: ChatContext): String? {
    577         return runBlocking {
    578             withReadyRemote(lastHandle) { remote, _ ->
    579                 remote.getUserPointerForContext(context.toDto())
    580             }
    581         }
    582     }
    583 
    584     override fun setUserPointerForContext(context: ChatContext, userPointer: String) {
    585         runBlocking {
    586             withReadyRemote(lastHandle) { remote, _ ->
    587                 remote.setUserPointerForContext(context.toDto(), userPointer)
    588             }
    589         }
    590     }
    591 
    592     override fun getSenderFromMessage(message: ChatMessage): ChatContact {
    593         return runBlocking {
    594             val contactDto = withReadyRemote(lastHandle) { remote, _ ->
    595                 remote.getSenderFromMessage(message.toDto())
    596             }
    597             contactDto.toLocal()
    598         }
    599     }
    600 
    601     override fun getGroupFromContext(context: ChatContext): ChatGroup? = safeSync(null, "getGroupFromContext") {
    602         runBlocking {
    603             val groupDto = withReadyRemote(lastHandle) { remote, _ ->
    604                 remote.getGroupFromContext(context.toDto())
    605             }
    606             groupDto.toLocal()
    607         }
    608     }
    609 
    610     override fun getMessageForGroupContact(group: ChatGroup, contact: ChatContact): ChatMessage {
    611         return runBlocking {
    612             val messageDto = withReadyRemote(lastHandle) { remote, _ ->
    613                 remote.getMessageForGroupContact(group.toDto(), contact.toDto())
    614             }
    615             messageDto.toLocal(ChatContext(null, null, false, false))
    616         }
    617     }
    618 
    619     override fun getMessageKind(message: ChatMessage): MessageKind {
    620         return runBlocking {
    621             val kind = withReadyRemote(lastHandle) { remote, _ ->
    622                 remote.getMessageKind(message.toDto())
    623             }
    624             MessageKind.fromCode(kind)
    625         }
    626     }
    627 
    628     override fun isMessageRecent(message: ChatMessage): GnunetReturnValue {
    629         return runBlocking {
    630             val result = withReadyRemote(lastHandle) { remote, _ ->
    631                 remote.isMessageRecent(message.toDto())
    632             }
    633             result.toGnunetReturn()
    634         }
    635     }
    636 
    637     override fun getMessageTimestamp(message: ChatMessage): Long {
    638         return runBlocking {
    639             withReadyRemote(lastHandle) { remote, _ ->
    640                 remote.getMessageTimestamp(message.toDto())
    641             }
    642         }
    643     }
    644 
    645     override fun setMessageForGroupContact(
    646         group: ChatGroup,
    647         contact: ChatContact,
    648         message: ChatMessage
    649     ) {
    650         runBlocking {
    651             withReadyRemote(lastHandle) { remote, _ ->
    652                 remote.setMessageForGroupContact(group.toDto(), contact.toDto(), message.toDto())
    653             }
    654         }
    655     }
    656 
    657     override suspend fun listContacts(handle: ChatHandle): List<ChatContact> {
    658         return withReadyRemote(handle) { remote, h ->
    659             val result = mutableListOf<ChatContact>()
    660             val done = CompletableDeferred<Unit>()
    661 
    662             val bridge = object : IContactCallback.Stub() {
    663                 override fun onContact(contactDto: ChatContactDto) {
    664                     result.add(contactDto.toLocal())
    665                 }
    666 
    667                 override fun onDone() {
    668                     if (!done.isCompleted) {
    669                         done.complete(Unit)
    670                     }
    671                 }
    672 
    673                 override fun onError(code: Int, message: String?) {
    674                     if (!done.isCompleted) {
    675                         done.completeExceptionally(
    676                             IllegalStateException("iterateContacts failed: $code ${message ?: ""}".trim())
    677                         )
    678                     }
    679                 }
    680             }
    681 
    682             remote.iterateContacts(h, bridge)
    683             done.await()
    684             result.toList()
    685         }
    686     }
    687 
    688     override suspend fun listGroups(handle: ChatHandle): List<ChatGroup> {
    689         return withReadyRemote(handle) { remote, h ->
    690             val result = mutableListOf<ChatGroup>()
    691             val done = CompletableDeferred<Unit>()
    692 
    693             val bridge = object : IGroupCallback.Stub() {
    694                 override fun onGroup(groupDto: ChatGroupDto) {
    695                     result.add(groupDto.toLocal())
    696                 }
    697 
    698                 override fun onDone() {
    699                     if (!done.isCompleted) {
    700                         done.complete(Unit)
    701                     }
    702                 }
    703 
    704                 override fun onError(code: Int, message: String?) {
    705                     if (!done.isCompleted) {
    706                         done.completeExceptionally(
    707                             IllegalStateException("iterateGroups failed: $code ${message ?: ""}".trim())
    708                         )
    709                     }
    710                 }
    711             }
    712 
    713             remote.iterateGroups(h, bridge)
    714             done.await()
    715             result.toList()
    716         }
    717     }
    718 
    719     override fun iterateContacts(handle: ChatHandle, callback: (ChatContact) -> Int) {
    720         val bridge = object : IContactCallback.Stub() {
    721             override fun onContact(contactDto: ChatContactDto) {
    722                 val contact = contactDto.toLocal()
    723                 mainScope.launch { callback(contact) }
    724             }
    725 
    726             override fun onDone() {
    727                 Log.d(TAG, "iterateContacts: done")
    728             }
    729 
    730             override fun onError(code: Int, message: String?) {
    731                 Log.e(TAG, "iterateContacts: error $code $message")
    732             }
    733         }
    734 
    735         val remote = remoteRef.get()
    736         if (remote != null) {
    737             ioScope.launch {
    738                 try {
    739                     remote.iterateContacts(handle.pointer, bridge)
    740                 } catch (e: RemoteException) {
    741                     Log.e(TAG, "iterateContacts failed", e)
    742                 }
    743             }
    744         } else {
    745             bind()
    746             synchronized(pendingAfterHandle) {
    747                 pendingAfterHandle += { r, real ->
    748                     runCatching { r.iterateContacts(real, bridge) }
    749                         .onFailure { Log.e(TAG, "iterateContacts (deferred) failed", it) }
    750                 }
    751             }
    752         }
    753     }
    754 
    755     override fun iterateGroups(handle: ChatHandle, callback: (ChatGroup) -> Int) {
    756         val bridge = object : IGroupCallback.Stub() {
    757             override fun onGroup(groupDto: ChatGroupDto) {
    758                 val group = groupDto.toLocal()
    759                 mainScope.launch { callback(group) }
    760             }
    761 
    762             override fun onDone() {
    763                 Log.d(TAG, "iterateGroups: done")
    764             }
    765 
    766             override fun onError(code: Int, message: String?) {
    767                 Log.e(TAG, "iterateGroups: error $code $message")
    768             }
    769         }
    770 
    771         val remote = remoteRef.get()
    772         if (remote != null) {
    773             ioScope.launch {
    774                 try {
    775                     remote.iterateGroups(handle.pointer, bridge)
    776                 } catch (e: RemoteException) {
    777                     Log.e(TAG, "iterateGroups failed", e)
    778                 }
    779             }
    780         } else {
    781             bind()
    782             synchronized(pendingAfterHandle) {
    783                 pendingAfterHandle += { r, real ->
    784                     runCatching { r.iterateGroups(real, bridge) }
    785                         .onFailure { Log.e(TAG, "iterateGroups (deferred) failed", it) }
    786                 }
    787             }
    788         }
    789     }
    790 
    791     override fun getContactContext(chatContact: ChatContact): ChatContext {
    792         return runBlocking {
    793             val contextDto = withReadyRemote(lastHandle) { remote, _ ->
    794                 remote.getContactContext(chatContact.toDto())
    795             }
    796             contextDto.toLocal()
    797         }
    798     }
    799 
    800     override fun getGroupContext(chatGroup: ChatGroup): ChatContext {
    801         return runBlocking {
    802             val contextDto = withReadyRemote(lastHandle) { remote, _ ->
    803                 remote.getGroupContext(chatGroup.toDto())
    804             }
    805             contextDto.toLocal()
    806         }
    807     }
    808 
    809     override fun getContactUserPointer(chatContact: ChatContact): String {
    810         return runBlocking {
    811             withReadyRemote(lastHandle) { remote, _ ->
    812                 remote.getContactUserPointer(chatContact.toDto())
    813             }
    814         }
    815     }
    816 
    817     override fun setContactUserPointer(chatContact: ChatContact, userPointer: String) {
    818         runBlocking {
    819             withReadyRemote(lastHandle) { remote, _ ->
    820                 remote.setContactUserPointer(chatContact.toDto(), userPointer)
    821             }
    822         }
    823     }
    824 
    825     override fun getGroupUserPointer(chatGroup: ChatGroup): String {
    826         return runBlocking {
    827             withReadyRemote(lastHandle) { remote, _ ->
    828                 remote.getGroupUserPointer(chatGroup.toDto())
    829             }
    830         }
    831     }
    832 
    833     override fun setGroupUserPointer(chatGroup: ChatGroup, userPointer: String) {
    834         runBlocking {
    835             withReadyRemote(lastHandle) { remote, _ ->
    836                 remote.setGroupUserPointer(chatGroup.toDto(), userPointer)
    837             }
    838         }
    839     }
    840 
    841     override fun sendText(chatContext: ChatContext, text: String) {
    842         val dto = chatContext.toDto()
    843         Log.d(
    844             TAG,
    845             "sendText[client]: nativeCtxPtr=${dto.nativeContextPointer} " +
    846                 "userPtr=${dto.userPointer} textLen=${text.length} " +
    847                 "lastHandle=${lastHandle.pointer}"
    848         )
    849         try {
    850             runBlocking {
    851                 withReadyRemote(lastHandle) { remote, _ ->
    852                     Log.d(TAG, "sendText[client]: calling AIDL remote.sendText...")
    853                     remote.sendText(dto, text)
    854                     Log.d(TAG, "sendText[client]: AIDL remote.sendText returned OK")
    855                 }
    856             }
    857         } catch (t: Throwable) {
    858             Log.e(TAG, "sendText[client]: AIDL call FAILED", t)
    859             throw t
    860         }
    861     }
    862 
    863     override fun getContactKey(chatContact: ChatContact): String {
    864         return runBlocking {
    865             withReadyRemote(lastHandle) { remote, _ ->
    866                 remote.getContactKey(chatContact.toDto())
    867             }
    868         }
    869     }
    870 
    871     override fun getContextContact(context: ChatContext): ChatContact {
    872         return runBlocking {
    873             val contactDto = withReadyRemote(lastHandle) { remote, _ ->
    874                 remote.getContextContact(context.toDto())
    875             }
    876             contactDto.toLocal()
    877         }
    878     }
    879 
    880     override fun deleteContact(chatContact: ChatContact) {
    881         runBlocking {
    882             withReadyRemote(lastHandle) { remote, _ ->
    883                 remote.deleteContact(chatContact.toDto())
    884             }
    885         }
    886     }
    887 
    888     override fun isGroup(context: ChatContext): Boolean = safeSync(false, "isGroup") {
    889         runBlocking {
    890             withReadyRemote(lastHandle) { remote, _ ->
    891                 remote.isGroup(context.toDto())
    892             }
    893         }
    894     }
    895 
    896     override fun isPlatform(context: ChatContext): Boolean = safeSync(false, "isPlatform") {
    897         runBlocking {
    898             withReadyRemote(lastHandle) { remote, _ ->
    899                 remote.isPlatform(context.toDto())
    900             }
    901         }
    902     }
    903 
    904     override fun iterateGroupContacts(
    905         chatGroup: ChatGroup,
    906         callback: (ChatGroup, ChatContact) -> Int
    907     ) {
    908         val bridge = object : IGroupContactCallback.Stub() {
    909             override fun onGroupContact(groupDto: ChatGroupDto, contactDto: ChatContactDto) {
    910                 val group = groupDto.toLocal()
    911                 val contact = contactDto.toLocal()
    912                 mainScope.launch { callback(group, contact) }
    913             }
    914 
    915             override fun onDone() {
    916                 Log.d(TAG, "iterateGroupContacts: done")
    917             }
    918 
    919             override fun onError(code: Int, message: String?) {
    920                 Log.e(TAG, "iterateGroupContacts: error $code $message")
    921             }
    922         }
    923 
    924         val remote = remoteRef.get()
    925         if (remote != null) {
    926             ioScope.launch {
    927                 try {
    928                     remote.iterateGroupContacts(chatGroup.toDto(), bridge)
    929                 } catch (e: RemoteException) {
    930                     Log.e(TAG, "iterateGroupContacts failed", e)
    931                 }
    932             }
    933         } else {
    934             bind()
    935             synchronized(pendingAfterHandle) {
    936                 pendingAfterHandle += { r, _ ->
    937                     runCatching { r.iterateGroupContacts(chatGroup.toDto(), bridge) }
    938                         .onFailure {
    939                             Log.e(TAG, "iterateGroupContacts (deferred) failed", it)
    940                         }
    941                 }
    942             }
    943         }
    944     }
    945 
    946     override suspend fun listGroupContacts(group: ChatGroup): List<ChatContact> {
    947         return withReadyRemote(lastHandle) { remote, _ ->
    948             val result = mutableListOf<ChatContact>()
    949             val done = CompletableDeferred<Unit>()
    950 
    951             val bridge = object : IGroupContactCallback.Stub() {
    952                 override fun onGroupContact(groupDto: ChatGroupDto, contactDto: ChatContactDto) {
    953                     result.add(contactDto.toLocal())
    954                 }
    955 
    956                 override fun onDone() {
    957                     if (!done.isCompleted) {
    958                         done.complete(Unit)
    959                     }
    960                 }
    961 
    962                 override fun onError(code: Int, message: String?) {
    963                     if (!done.isCompleted) {
    964                         done.completeExceptionally(
    965                             IllegalStateException("iterateGroupContacts failed: $code ${message ?: ""}".trim())
    966                         )
    967                     }
    968                 }
    969             }
    970 
    971             remote.iterateGroupContacts(group.toDto(), bridge)
    972             done.await()
    973             result.toList()
    974         }
    975     }
    976 
    977     override fun randomUUID(): String {
    978         return "uuid_${System.currentTimeMillis()}_${uuidCounter++}"
    979     }
    980 
    981     override fun getContactAttributes(contact: ChatContact, callback: (String, String) -> Unit) {
    982         val bridge = object : IAttributeCallback.Stub() {
    983             override fun onAttribute(key: String, value: String) {
    984                 mainScope.launch { callback(key, value) }
    985             }
    986 
    987             override fun onDone() {
    988                 Log.d(TAG, "getContactAttributes: done")
    989             }
    990 
    991             override fun onError(code: Int, message: String?) {
    992                 Log.e(TAG, "getContactAttributes: error $code $message")
    993             }
    994         }
    995 
    996         val remote = remoteRef.get()
    997         if (remote != null) {
    998             ioScope.launch {
    999                 try {
   1000                     remote.getContactAttributes(contact.toDto(), bridge)
   1001                 } catch (e: RemoteException) {
   1002                     Log.e(TAG, "getContactAttributes failed", e)
   1003                 }
   1004             }
   1005         } else {
   1006             bind()
   1007             synchronized(pendingAfterHandle) {
   1008                 pendingAfterHandle += { r, _ ->
   1009                     runCatching { r.getContactAttributes(contact.toDto(), bridge) }
   1010                         .onFailure {
   1011                             Log.e(TAG, "getContactAttributes (deferred) failed", it)
   1012                         }
   1013                 }
   1014             }
   1015         }
   1016     }
   1017 
   1018     override fun shareAttributes(handle: ChatHandle, contact: ChatContact, key: String) {
   1019         runBlocking {
   1020             withReadyRemote(handle) { remote, h ->
   1021                 remote.shareAttributes(h, contact.toDto(), key)
   1022             }
   1023         }
   1024     }
   1025 
   1026     override fun unshareAttributes(handle: ChatHandle, contact: ChatContact, key: String) {
   1027         runBlocking {
   1028             withReadyRemote(handle) { remote, h ->
   1029                 remote.unshareAttributes(h, contact.toDto(), key)
   1030             }
   1031         }
   1032     }
   1033 
   1034     override suspend fun iterateContextMessages(context: ChatContext): List<ChatMessage> {
   1035         val messages = mutableListOf<ChatMessage>()
   1036         val done = CompletableDeferred<Unit>()
   1037 
   1038         withReadyRemote(lastHandle) { remote, _ ->
   1039             val cb = object : IMessageIterateCallback.Stub() {
   1040                 override fun onMessage(message: ChatMessageDto) {
   1041                     val msg = message.toLocal(context)
   1042                     messages.add(msg)
   1043                 }
   1044                 override fun onDone() {
   1045                     done.complete(Unit)
   1046                 }
   1047                 override fun onError(code: Int, message: String?) {
   1048                     done.completeExceptionally(
   1049                         RuntimeException("iterateContextMessages failed: $code $message")
   1050                     )
   1051                 }
   1052             }
   1053             remote.iterateContextMessages(context.toDto(), cb)
   1054         }
   1055 
   1056         done.await()
   1057         return messages
   1058     }
   1059 
   1060     companion object {
   1061         private const val TAG = "GnunetChatBoundService"
   1062         private const val ACTION_BIND_GNUNET_CHAT =
   1063             "org.gnunet.gnunetmessenger.ipc.BIND_GNUNET_CHAT"
   1064         private const val SERVER_PACKAGE = "org.gnu.gnunet"
   1065         private const val DEFAULT_APP_NAME = "Default"
   1066     }
   1067 }