1 /*
2  * Copyright 2024 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.core.telecom.internal
18 
19 import android.net.Uri
20 import android.util.Log
21 import androidx.core.telecom.CallException
22 import androidx.core.telecom.extensions.Extensions
23 import androidx.core.telecom.extensions.IActionsResultCallback
24 import androidx.core.telecom.extensions.ICallDetailsListener
25 import androidx.core.telecom.extensions.ICallIconStateListener
26 import androidx.core.telecom.extensions.ICapabilityExchange
27 import androidx.core.telecom.extensions.ICapabilityExchangeListener
28 import androidx.core.telecom.extensions.ILocalSilenceActions
29 import androidx.core.telecom.extensions.ILocalSilenceStateListener
30 import androidx.core.telecom.extensions.IMeetingSummaryStateListener
31 import androidx.core.telecom.extensions.IParticipantActions
32 import androidx.core.telecom.extensions.IParticipantStateListener
33 import androidx.core.telecom.extensions.Participant
34 import androidx.core.telecom.extensions.ParticipantParcelable
35 import androidx.core.telecom.util.ExperimentalAppActions
36 import kotlinx.coroutines.CoroutineScope
37 import kotlinx.coroutines.cancel
38 import kotlinx.coroutines.launch
39 
40 /** Remote interface for communicating back to the remote interface the result of an Action. */
41 @ExperimentalAppActions
42 internal class ActionsResultCallbackRemote(binder: IActionsResultCallback) :
43     IActionsResultCallback by binder
44 
45 /**
46  * Implements the binder interface that is used by the remote InCallService in order to perform an
47  * Action and calls the delegate callback function if an action has registered to listen.
48  */
49 @ExperimentalAppActions
50 internal class ParticipantActionCallbackRepository(coroutineScope: CoroutineScope) {
51     companion object {
52         private const val LOG_TAG = Extensions.LOG_TAG + "(PACR)"
53     }
54 
55     /**
56      * The callback that is called when the remote InCallService changes the raised hand state of
57      * this user.
58      */
59     var raiseHandStateCallback: (suspend (Boolean) -> Unit)? = null
60 
61     /**
62      * The callback that is called when the remote InCallService requests to kick a participant
63      * using its id.
64      */
65     var kickParticipantCallback: (suspend (String) -> Unit)? = null
66 
67     /** Listener used to handle event callbacks from the remote. */
68     val eventListener =
69         object : IParticipantActions.Stub() {
setHandRaisednull70             override fun setHandRaised(handRaisedState: Boolean, cb: IActionsResultCallback?) {
71                 cb?.let {
72                     coroutineScope.launch {
73                         Log.i(LOG_TAG, "from remote: raiseHandStateChanged=$handRaisedState")
74                         raiseHandStateCallback?.invoke(handRaisedState)
75                         ActionsResultCallbackRemote(cb).onSuccess()
76                     }
77                 }
78             }
79 
kickParticipantnull80             override fun kickParticipant(participantId: String, cb: IActionsResultCallback?) {
81                 cb?.let {
82                     coroutineScope.launch {
83                         Log.i(LOG_TAG, "from remote: kickParticipant=$participantId")
84                         kickParticipantCallback?.invoke(participantId)
85                         ActionsResultCallbackRemote(cb).onSuccess()
86                     }
87                 }
88             }
89         }
90 }
91 
92 /** Remote interface used by InCallServices to send action events to the VOIP application. */
93 @ExperimentalAppActions
94 internal class ParticipantActionsRemote(binder: IParticipantActions) :
<lambda>null95     IParticipantActions by binder {
96     fun kickParticipant(participant: Participant, cb: IActionsResultCallback?) {
97         kickParticipant(participant.id, cb)
98     }
99 }
100 
101 /**
102  * Remote interface used to notify the ICS of participant state information
103  *
104  * @param binder The remote binder interface to wrap
105  */
106 @ExperimentalAppActions
107 internal class ParticipantStateListenerRemote(private val binder: IParticipantStateListener) {
updateParticipantsnull108     fun updateParticipants(participants: List<Participant>) {
109         binder.updateParticipants(
110             participants.map(Participant::toParticipantParcelable).toTypedArray()
111         )
112     }
113 
updateActiveParticipantnull114     fun updateActiveParticipant(activeParticipant: Participant?) {
115         binder.updateActiveParticipant(activeParticipant?.id)
116     }
117 
updateRaisedHandsActionnull118     fun updateRaisedHandsAction(participants: List<Participant>) {
119         binder.updateRaisedHandsAction(participants.map { it.id }.toTypedArray())
120     }
121 
finishSyncnull122     fun finishSync(actions: IParticipantActions) {
123         binder.finishSync(actions)
124     }
125 }
126 
127 @ExperimentalAppActions
128 internal class CallIconStateListenerRemote(val binder: ICallIconStateListener) {
updateCallIconUrinull129     fun updateCallIconUri(uri: Uri) {
130         binder.updateCallIconUri(uri)
131     }
132 
finishSyncnull133     fun finishSync() {
134         binder.finishSync()
135     }
136 }
137 
138 @ExperimentalAppActions
139 internal class CallIconStateListener(
140     private val callIconUriUpdater: (Uri) -> Unit,
141     private val finishSync: (Unit) -> Unit
142 ) : ICallIconStateListener.Stub() {
updateCallIconUrinull143     override fun updateCallIconUri(uri: Uri) {
144         callIconUriUpdater.invoke(uri)
145     }
146 
finishSyncnull147     override fun finishSync() {
148         finishSync.invoke(Unit)
149     }
150 }
151 
152 @ExperimentalAppActions
153 internal class MeetingSummaryStateListenerRemote(val binder: IMeetingSummaryStateListener) {
154 
updateCurrentSpeakernull155     fun updateCurrentSpeaker(speakerName: String) {
156         binder.updateCurrentSpeaker(speakerName)
157     }
158 
updateParticipantCountnull159     fun updateParticipantCount(participantCount: Int) {
160         binder.updateParticipantCount(participantCount)
161     }
162 
finishSyncnull163     fun finishSync() {
164         binder.finishSync()
165     }
166 }
167 
168 @ExperimentalAppActions
169 internal class MeetingSummaryStateListener(
170     private val updateCurrentSpeaker: (String) -> Unit,
171     private val updateParticipantCount: (Int) -> Unit,
172     private val finishSync: (Unit) -> Unit
173 ) : IMeetingSummaryStateListener.Stub() {
174 
updateCurrentSpeakernull175     override fun updateCurrentSpeaker(speakerName: String) {
176         updateCurrentSpeaker.invoke(speakerName)
177     }
178 
updateParticipantCountnull179     override fun updateParticipantCount(participantCount: Int) {
180         updateParticipantCount.invoke(participantCount)
181     }
182 
finishSyncnull183     override fun finishSync() {
184         finishSync.invoke(Unit)
185     }
186 }
187 
188 @ExperimentalAppActions
189 internal class LocalCallSilenceActionsRemote(binder: ILocalSilenceActions) :
190     ILocalSilenceActions by binder
191 
192 @ExperimentalAppActions
193 internal class LocalCallSilenceStateListenerRemote(val binder: ILocalSilenceStateListener) {
updateIsLocallySilencednull194     fun updateIsLocallySilenced(isLocallySilenced: Boolean) {
195         binder.updateIsLocallySilenced(isLocallySilenced)
196     }
197 
finishSyncnull198     fun finishSync(actions: ILocalSilenceActions) {
199         binder.finishSync(actions)
200     }
201 }
202 
203 @ExperimentalAppActions
204 internal class LocalCallSilenceCallbackRepository(coroutineScope: CoroutineScope) {
205     var localCallSilenceCallback: (suspend (Boolean) -> Unit)? = null
206 
207     val eventListener =
208         object : ILocalSilenceActions.Stub() {
setIsLocallySilencednull209             override fun setIsLocallySilenced(
210                 isLocallySilenced: Boolean,
211                 cb: IActionsResultCallback?
212             ) {
213                 cb?.let {
214                     coroutineScope.launch {
215                         if (localCallSilenceCallback == null) {
216                             ActionsResultCallbackRemote(cb)
217                                 .onFailure(
218                                     CallException.ERROR_UNKNOWN,
219                                     "localCallSilenceCallback is NULL"
220                                 )
221                         } else {
222                             localCallSilenceCallback?.invoke(isLocallySilenced)
223                             ActionsResultCallbackRemote(cb).onSuccess()
224                         }
225                     }
226                 }
227             }
228         }
229 }
230 
231 @ExperimentalAppActions
232 internal class LocalCallSilenceStateListener(
233     private val updateLocalCallSilence: (Boolean) -> Unit,
234     private val finishSync: (LocalCallSilenceActionsRemote?) -> Unit
235 ) : ILocalSilenceStateListener.Stub() {
updateIsLocallySilencednull236     override fun updateIsLocallySilenced(isLocallySilenced: Boolean) {
237         updateLocalCallSilence.invoke(isLocallySilenced)
238     }
239 
finishSyncnull240     override fun finishSync(cb: ILocalSilenceActions?) {
241         finishSync.invoke(cb?.let { LocalCallSilenceActionsRemote(it) })
242     }
243 }
244 
245 /**
246  * The remote interface used to begin capability exchange with the InCallService.
247  *
248  * @param binder the remote binder interface.
249  */
250 @ExperimentalAppActions
251 internal class CapabilityExchangeRemote(binder: ICapabilityExchange) :
252     ICapabilityExchange by binder
253 
254 /**
255  * Remote interface for [ICapabilityExchangeListener] that InCallServices use to communicate with
256  * the remote VOIP application.
257  */
258 @ExperimentalAppActions
259 internal class CapabilityExchangeListenerRemote(binder: ICapabilityExchangeListener) :
260     ICapabilityExchangeListener by binder
261 
262 /**
263  * Adapter class that implements [IParticipantStateListener] AIDL and calls the associated callbacks
264  */
265 @ExperimentalAppActions
266 internal class ParticipantStateListener(
267     private val updateParticipants: (Set<Participant>) -> Unit,
268     private val updateActiveParticipantId: (String?) -> Unit,
269     private val updateRaisedHandIds: (List<String>) -> Unit,
270     private val finishSync: (ParticipantActionsRemote?) -> Unit
271 ) : IParticipantStateListener.Stub() {
updateParticipantsnull272     override fun updateParticipants(participants: Array<out ParticipantParcelable>?) {
273         updateParticipants.invoke(
274             participants?.map { Participant(it.id, it.name) }?.toSet() ?: emptySet()
275         )
276     }
277 
updateActiveParticipantnull278     override fun updateActiveParticipant(activeParticipantId: String?) {
279         updateActiveParticipantId.invoke(activeParticipantId)
280     }
281 
updateRaisedHandsActionnull282     override fun updateRaisedHandsAction(participants: Array<out String>?) {
283         updateRaisedHandIds.invoke(participants?.toList() ?: emptyList())
284     }
285 
finishSyncnull286     override fun finishSync(cb: IParticipantActions?) {
287         if (cb == null) {
288             Log.w("AidlExtensions", "finishSync returned null actions!")
289         }
290         finishSync.invoke(cb?.let { ParticipantActionsRemote(it) })
291     }
292 }
293 
294 /**
295  * The repository containing the methods used during capability exchange to create each extension.
296  * Extensions will use this to register themselves as handlers of these callbacks.
297  *
298  * @param connectionScope The [CoroutineScope] that governs this connection to the remote. This
299  *   scope will be cancelled by this class when the remote notifies us that the connection is being
300  *   torn down.
301  */
302 @ExperimentalAppActions
303 internal class CapabilityExchangeRepository(private val connectionScope: CoroutineScope) {
304 
305     /** A request to create the ParticipantExtension has been received */
306     var onCreateParticipantExtension:
307         ((CoroutineScope, Set<Int>, ParticipantStateListenerRemote) -> Unit)? =
308         null
309 
310     // This is set in LocalSilenceExtensionImpl (VoIP side) in onExchangeStarted(...)
311     // callbacks.onCreateLocalCallSilenceExtension = // current impl
312     var onCreateLocalCallSilenceExtension:
313         ((CoroutineScope, Set<Int>, LocalCallSilenceStateListenerRemote) -> Unit)? =
314         null
315 
316     var onCreateCallIconExtension:
317         ((CoroutineScope, Set<Int>, String, CallIconStateListenerRemote) -> Unit)? =
318         null
319 
320     var onMeetingSummaryExtension: ((CoroutineScope, MeetingSummaryStateListenerRemote) -> Unit)? =
321         null
322 
323     val listener =
324         object : ICapabilityExchangeListener.Stub() {
onCreateParticipantExtensionnull325             override fun onCreateParticipantExtension(
326                 version: Int,
327                 actions: IntArray?,
328                 l: IParticipantStateListener?
329             ) {
330                 l?.let {
331                     onCreateParticipantExtension?.invoke(
332                         connectionScope,
333                         actions?.toSet() ?: emptySet(),
334                         ParticipantStateListenerRemote(l)
335                     )
336                 }
337             }
338 
onCreateLocalCallSilenceExtensionnull339             override fun onCreateLocalCallSilenceExtension(
340                 version: Int,
341                 actions: IntArray?,
342                 l: ILocalSilenceStateListener?
343             ) {
344                 l?.let {
345                     // called by the LocalSilenceExtensionImpl (VoIP side)
346                     onCreateLocalCallSilenceExtension?.invoke(
347                         connectionScope,
348                         actions?.toSet() ?: emptySet(),
349                         LocalCallSilenceStateListenerRemote(l)
350                     )
351                 }
352             }
353 
onCreateCallIconExtensionnull354             override fun onCreateCallIconExtension(
355                 version: Int,
356                 actions: IntArray?,
357                 remoteName: String,
358                 l: ICallIconStateListener?
359             ) {
360                 l?.let {
361                     onCreateCallIconExtension?.invoke(
362                         connectionScope,
363                         actions?.toSet() ?: emptySet(),
364                         remoteName,
365                         CallIconStateListenerRemote(l)
366                     )
367                 }
368             }
369 
onCreateMeetingSummaryExtensionnull370             override fun onCreateMeetingSummaryExtension(
371                 version: Int,
372                 l: IMeetingSummaryStateListener?
373             ) {
374                 l?.let {
375                     onMeetingSummaryExtension?.invoke(
376                         connectionScope,
377                         MeetingSummaryStateListenerRemote(l)
378                     )
379                 }
380             }
381 
onCreateCallDetailsExtensionnull382             override fun onCreateCallDetailsExtension(
383                 version: Int,
384                 actions: IntArray?,
385                 l: ICallDetailsListener?,
386                 packageName: String?
387             ) {
388                 TODO("Not yet implemented")
389             }
390 
onRemoveExtensionsnull391             override fun onRemoveExtensions() {
392                 connectionScope.cancel("remote has removed extensions")
393             }
394         }
395 }
396