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