1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.datastore.testapp.twoWayIpc 18 19 import android.content.ComponentName 20 import android.content.Context 21 import android.content.Intent 22 import android.content.ServiceConnection 23 import android.content.pm.PackageManager 24 import android.os.Handler 25 import android.os.IBinder 26 import android.os.Looper 27 import android.os.Message 28 import android.os.Messenger 29 import kotlin.time.Duration.Companion.seconds 30 import kotlinx.coroutines.CompletableDeferred 31 import kotlinx.coroutines.CoroutineScope 32 import kotlinx.coroutines.withTimeout 33 34 /** A [ServiceConnection] implementation that talks to an instance of [TwoWayIpcService]. */ 35 class TwoWayIpcConnection( 36 private val context: Context, 37 private val klass: Class<out TwoWayIpcService>, 38 ) : ServiceConnection { 39 private val connectionEstablished = CompletableDeferred<Messenger>() 40 withConnectionTimeoutnull41 private suspend fun <T> withConnectionTimeout(block: suspend () -> T): T { 42 return withTimeout(TIMEOUT) { block() } 43 } 44 connectnull45 suspend fun connect() { 46 val intent = Intent(context, klass) 47 withConnectionTimeout { 48 val serviceExists: Boolean = context.bindService(intent, this, Context.BIND_AUTO_CREATE) 49 50 if (!serviceExists) { 51 val targetPackage: String = intent.component!!.packageName 52 val targetService: String = intent.component!!.className 53 54 try { 55 context.packageManager.getPackageInfo(targetPackage, 0) 56 } catch (e: PackageManager.NameNotFoundException) { 57 throw IllegalStateException("Package not installed [$targetPackage]", e) 58 } 59 throw IllegalStateException( 60 "Package installed but service not found [$targetService]" 61 ) 62 } 63 connectionEstablished.await() 64 } 65 } 66 disconnectnull67 suspend fun disconnect() { 68 sendMessage(Message.obtain().also { it.what = TwoWayIpcService.MSG_DESTROY_SUBJECTS }) 69 context.unbindService(this) 70 } 71 <lambda>null72 private suspend fun sendMessage(message: Message): Message = withConnectionTimeout { 73 val response = CompletableDeferred<Message>() 74 message.replyTo = 75 Messenger( 76 object : Handler(Looper.getMainLooper()) { 77 override fun handleMessage(msg: Message) { 78 if (msg.what == TwoWayIpcService.MSG_CREATE_SUBJECT) { 79 val stacktrace = 80 msg.data.getString("ipc_stacktrace") ?: "missing stacktrace" 81 response.completeExceptionally( 82 AssertionError("Exception in remote process: $stacktrace") 83 ) 84 } else { 85 response.complete(Message.obtain().also { it.copyFrom(msg) }) 86 } 87 } 88 } 89 ) 90 connectionEstablished.await().send(message) 91 response.await() 92 } 93 onServiceConnectednull94 override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { 95 connectionEstablished.complete(Messenger(binder)) 96 } 97 onServiceDisconnectednull98 override fun onServiceDisconnected(componentName: ComponentName) { 99 // this is called only if the service crashes 100 } 101 createSubjectnull102 internal suspend fun createSubject( 103 hostExecutionScope: CoroutineScope, 104 ): TwoWayIpcSubject { 105 val hostSubject = TwoWayIpcSubject(datastoreScope = hostExecutionScope) 106 val message = Message.obtain() 107 message.what = TwoWayIpcService.MSG_CREATE_SUBJECT 108 message.data.putParcelable("messenger", hostSubject.bus.incomingMessenger) 109 val response = sendMessage(message) 110 111 @Suppress("DEPRECATION") 112 val outgoingMessenger = response.data.getParcelable<Messenger>("messenger") 113 checkNotNull(outgoingMessenger) { "didn't receive an outgoing messenger" } 114 hostSubject.bus.setOutgoingMessenger(outgoingMessenger) 115 return hostSubject 116 } 117 118 companion object { 119 val TIMEOUT = 5.seconds 120 } 121 } 122