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 }