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