1 /* <lambda>null2 * 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.extensions 18 19 import android.os.Build 20 import android.util.Log 21 import androidx.annotation.RequiresApi 22 import androidx.core.telecom.CallControlResult 23 import androidx.core.telecom.CallException 24 import androidx.core.telecom.internal.CapabilityExchangeListenerRemote 25 import androidx.core.telecom.internal.LocalCallSilenceActionsRemote 26 import androidx.core.telecom.internal.LocalCallSilenceStateListener 27 import androidx.core.telecom.util.ExperimentalAppActions 28 import kotlin.coroutines.resume 29 import kotlin.properties.Delegates 30 import kotlinx.coroutines.CoroutineScope 31 import kotlinx.coroutines.flow.MutableStateFlow 32 import kotlinx.coroutines.flow.drop 33 import kotlinx.coroutines.flow.launchIn 34 import kotlinx.coroutines.flow.onEach 35 import kotlinx.coroutines.launch 36 import kotlinx.coroutines.suspendCancellableCoroutine 37 38 @RequiresApi(Build.VERSION_CODES.O) 39 @OptIn(ExperimentalAppActions::class) 40 internal class LocalCallSilenceExtensionRemoteImpl( 41 private val callScope: CoroutineScope, 42 private val onLocalSilenceStateUpdated: suspend (Boolean) -> Unit 43 ) : LocalCallSilenceExtensionRemote { 44 45 companion object { 46 val TAG: String = LocalCallSilenceExtensionRemoteImpl::class.java.simpleName 47 } 48 49 override var isSupported: Boolean by Delegates.notNull() 50 private val isLocallySilenced = MutableStateFlow(false) 51 private var remoteActions: ILocalSilenceActions? = null 52 53 /** 54 * This method is used by the InCallService to update the VoIP applications local call silence 55 * state. 56 */ 57 override suspend fun requestLocalCallSilenceUpdate(isSilenced: Boolean): CallControlResult { 58 if (remoteActions == null) { 59 Log.i(TAG, "requestLocalCallSilenceState: remoteActions are null") 60 return CallControlResult.Error(CallException.ERROR_UNKNOWN) 61 } 62 val cb = ActionsResultCallback() 63 // this remote impl --> VoIP / Callback 64 remoteActions?.setIsLocallySilenced(isSilenced, cb) 65 val result = cb.waitForResponse() 66 Log.i(TAG, "requestLocalCallSilenceState: isSilenced= $isSilenced, result=$result") 67 return result 68 } 69 70 // NOTE: There are NO actions! Therefore there is no need to add action support OR register! 71 internal val actions 72 get() = IntArray(0) 73 74 internal suspend fun onExchangeComplete( 75 negotiatedCapability: Capability?, 76 remote: CapabilityExchangeListenerRemote? 77 ) { 78 if (negotiatedCapability == null || remote == null) { 79 Log.i(TAG, "onNegotiated: remote is not capable") 80 isSupported = false 81 return 82 } 83 isSupported = true 84 Log.i(TAG, "onExchangeComplete: isSupported=[true]") 85 isLocallySilenced 86 .drop(1) // ignore the first default value 87 .onEach { 88 // This updates external extension block that the InCallService implements. 89 // see [CallExtensionScopeImpl#addLocalCallSilenceExtension] for more. 90 onLocalSilenceStateUpdated(it) 91 } 92 .launchIn(callScope) 93 94 remoteActions = connectToRemote(negotiatedCapability, remote) 95 } 96 97 private suspend fun connectToRemote( 98 negotiatedCapability: Capability, 99 remote: CapabilityExchangeListenerRemote 100 ): LocalCallSilenceActionsRemote? = suspendCancellableCoroutine { continuation -> 101 val stateListener = 102 LocalCallSilenceStateListener( 103 updateLocalCallSilence = { 104 callScope.launch { 105 // This is the first entry point when the VoIP app updates this 106 // remote impl. It is called when: 107 // - the initial sync is started 108 // - any update the VoIP app sends to this remote impl. 109 110 // This updates external extension block that the InCallService implements. 111 // see [CallExtensionScopeImpl#addLocalCallSilenceExtension] for more. 112 Log.i(TAG, "LCS_SL: updateLocalCallSilence: isSilenced=[$it]") 113 isLocallySilenced.emit(it) 114 } 115 }, 116 finishSync = { remoteBinder -> 117 callScope.launch { continuation.resume(remoteBinder) } 118 } 119 ) 120 remote.onCreateLocalCallSilenceExtension( 121 negotiatedCapability.featureVersion, 122 negotiatedCapability.supportedActions, 123 stateListener 124 ) 125 } 126 } 127