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