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.core.telecom
18 
19 import android.os.ParcelUuid
20 import kotlinx.coroutines.CoroutineScope
21 import kotlinx.coroutines.flow.Flow
22 
23 /**
24  * DSL interface to provide and receive updates about a call session. The CallControlScope should be
25  * used to provide updates (via the CallControlScope suspend functions) and receive updates (via the
26  * lambda functions) about the call. The CallControlScope will run for the duration of the call. To
27  * see an example implementation of the CallControlScope, please refer to the sample app on
28  * [github.](https://github.com/android/platform-samples/blob/3856087f7f0fa4901e521f1dc79a30ddfd108f4e/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/model/TelecomCallRepository.kt#L122)
29  *
30  * Example usage:
31  *
32  * // initiate a call and control via the CallControlScope mCallsManager.addCall( callAttributes,
33  * onAnswerLambda, onDisconnectLambda, onSetActiveLambda, onSetInActiveLambda ) { // This block
34  * represents the CallControlScope. Once Telecom has added the call to the // system, your
35  * application can start changing the call state (via setActive(), etc.), // collect the flows.
36  *
37  *          // Your application should gate ALL CallControlScope suspend functions with UI logic or
38  *          // logic that signals the call state is ready to be changed
39  *          launch {
40  *                 when (val res = setActive() ) {
41  *                   is CallControlResult.Success -> {
42  *                     // Telecom can place the active
43  *                     // update your call state and handle UI
44  *                   }
45  *                   is CallControlResult.Error -> {
46  *                     // Telecom cannot set your VoIP call active. Maybe there is an ongoing
47  *                     // active call that cannot be held. Check the failure code to determine the
48  *                     // recommended next action
49  *                     handleErrorCode( res.errorCode )
50  *                   }
51  *                  }
52  *                }
53  *           }
54  *          // Collect updates
55  *          launch {
56  *             currentCallEndpoint.collect { // access the new [CallEndpoint] here }
57  *          }
58  *          launch {
59  *             availableEndpoints.collect { // access the available [CallEndpoint]s here }
60  *          }
61  *          launch {
62  *             isMuted.collect { // access to the mute state }
63  *          }
64  *      }
65  *
66  * **Note:** Each [Flow] must be wrapped in an individual launch block or the [Flow] will not be
67  * collected.
68  */
69 public interface CallControlScope : CoroutineScope {
70     /**
71      * @return the 128-bit universally unique identifier Telecom assigned to this CallControlScope.
72      *   This id can be helpful for debugging when dumping the telecom system.
73      */
getCallIdnull74     public fun getCallId(): ParcelUuid
75 
76     /**
77      * Inform Telecom that your app wants to make this call active. This method should be called
78      * when either an outgoing call is ready to go active or a held call is ready to go active
79      * again. For incoming calls that are ready to be answered, use [answer].
80      *
81      * @return Telecom will return [CallControlResult.Success] if your app is able to set the call
82      *   active. Otherwise [CallControlResult.Error] will be returned (ex. another call is active
83      *   and telecom cannot set this call active until the other call is held or disconnected) with
84      *   an error code indicating why setActive failed.
85      */
86     public suspend fun setActive(): CallControlResult
87 
88     /**
89      * Inform Telecom that your app wants to make this call inactive. This the same as hold for two
90      * call endpoints but can be extended to setting a meeting to inactive.
91      *
92      * @return Telecom will return [CallControlResult.Success] if your app is able to set the call
93      *   inactive. Otherwise, [CallControlResult.Error] will be returned with an error code
94      *   indicating why setInActive failed.
95      */
96     public suspend fun setInactive(): CallControlResult
97 
98     /**
99      * Inform Telecom that your app wants to make this incoming call active. For outgoing calls and
100      * calls that have been placed on hold, use [setActive].
101      *
102      * @param [callType] that call is to be answered as.
103      * @return Telecom will return [CallControlResult.Success] if your app is able to answer the
104      *   call. Otherwise [CallControlResult.Error] will be returned with an error code indicating
105      *   why answer failed (ex. another call is active and telecom cannot set this call active until
106      *   the other call is held or disconnected). This means that your app cannot answer this call
107      *   at this time.
108      */
109     public suspend fun answer(
110         @CallAttributesCompat.Companion.CallType callType: Int
111     ): CallControlResult
112 
113     /**
114      * Inform Telecom that your app wishes to disconnect the call and remove the call from telecom
115      * tracking.
116      *
117      * @param disconnectCause represents the cause for disconnecting the call. The only valid codes
118      *   for the [android.telecom.DisconnectCause] passed in are: <ul>
119      *     <li>[DisconnectCause#LOCAL]</li>
120      *     <li>[DisconnectCause#REMOTE]</li>
121      *     <li>[DisconnectCause#REJECTED]</li>
122      *     <li>[DisconnectCause#MISSED]</li> </ul>
123      *
124      * @return [CallControlResult.Success] will be returned if Telecom is able to disconnect the
125      *   call successfully. Otherwise [CallControlResult.Error] will be returned with an error code
126      *   indicating why disconnect failed.
127      */
128     public suspend fun disconnect(
129         disconnectCause: android.telecom.DisconnectCause
130     ): CallControlResult
131 
132     /**
133      * Request a [CallEndpointCompat] change. Clients should not define their own
134      * [CallEndpointCompat] when requesting a change. Instead, the new [endpoint] should be one of
135      * the valid [CallEndpointCompat]s provided by [availableEndpoints].
136      *
137      * @param endpoint The [CallEndpointCompat] to change to.
138      * @return [CallControlResult.Success] will be returned if Telecom is able to switch to the
139      *   requested endpoint successfully. Otherwise, [CallControlResult.Error] will be returned with
140      *   an error code indicating why disconnect failed.
141      */
142     public suspend fun requestEndpointChange(endpoint: CallEndpointCompat): CallControlResult
143 
144     /**
145      * Collect the new [CallEndpointCompat] through which call media flows (i.e. speaker, bluetooth,
146      * etc.).
147      */
148     public val currentCallEndpoint: Flow<CallEndpointCompat>
149 
150     /** Collect the set of available [CallEndpointCompat]s reported by Telecom. */
151     public val availableEndpoints: Flow<List<CallEndpointCompat>>
152 
153     /**
154      * Collect the current mute state of the call. This Flow is updated every time the mute state
155      * changes.
156      */
157     public val isMuted: Flow<Boolean>
158 }
159