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.net.Uri
20 import android.telecom.PhoneAccountHandle
21 import androidx.annotation.IntDef
22 import androidx.annotation.RequiresApi
23 import androidx.annotation.RestrictTo
24 import androidx.core.telecom.internal.utils.CallAttributesUtils
25 import androidx.core.telecom.internal.utils.Utils
26 import java.util.Objects
27 
28 /**
29  * CallAttributes represents a set of properties that define a new Call. Applications should build
30  * an instance of this class and use [CallsManager.addCall] to start a new call with Telecom.
31  *
32  * @param displayName Display name of the person on the other end of the call
33  * @param address Address of the call. Note, this can be extended to a meeting link or a custom
34  *   scheme. On sdks 26 & 27, the address should start with the "sip:" prefix (e.g
35  *   Uri.parse("sip:")), otherwise the address will be replaced with "sip:" + packageName.
36  * @param direction The direction (Outgoing/Incoming) of the new Call
37  * @param callType Information related to data being transmitted (voice, video, etc. )
38  * @param callCapabilities Allows a package to opt into capabilities on the telecom side, on a
39  *   per-call basis
40  * @param preferredStartingCallEndpoint allows clients to specify a [CallEndpointCompat] to start a
41  *   new call on. The [preferredStartingCallEndpoint] should be a value returned from
42  *   [CallsManager.getAvailableStartingCallEndpoints]. Once the call is started, Core-Telecom will
43  *   switch to the [preferredStartingCallEndpoint] before running the [CallControlScope].
44  */
45 public class CallAttributesCompat(
46     public val displayName: CharSequence,
47     public val address: Uri,
48     @Direction public val direction: Int,
49     @CallType public val callType: Int = CALL_TYPE_AUDIO_CALL,
50     @CallCapability public val callCapabilities: Int = SUPPORTS_SET_INACTIVE,
51     public val preferredStartingCallEndpoint: CallEndpointCompat? = null
52 ) {
53     internal var mHandle: PhoneAccountHandle? = null
54 
toStringnull55     override fun toString(): String {
56         return "CallAttributes(" +
57             "displayName=[$displayName], " +
58             "address=[$address], " +
59             "direction=[${directionToString()}], " +
60             "callType=[${callTypeToString()}], " +
61             "capabilities=[${capabilitiesToString()}])"
62     }
63 
equalsnull64     override fun equals(other: Any?): Boolean {
65         return other is CallAttributesCompat &&
66             displayName == other.displayName &&
67             address == other.address &&
68             direction == other.direction &&
69             callType == other.callType &&
70             callCapabilities == other.callCapabilities
71     }
72 
hashCodenull73     override fun hashCode(): Int {
74         return Objects.hash(displayName, address, direction, callType, callCapabilities)
75     }
76 
77     public companion object {
78         @RestrictTo(RestrictTo.Scope.LIBRARY)
79         @Retention(AnnotationRetention.SOURCE)
80         @IntDef(DIRECTION_INCOMING, DIRECTION_OUTGOING)
81         @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
82         public annotation class Direction
83 
84         /** Indicates that the call is an incoming call. */
85         public const val DIRECTION_INCOMING: Int = 1
86 
87         /** Indicates that the call is an outgoing call. */
88         public const val DIRECTION_OUTGOING: Int = 2
89 
90         @RestrictTo(RestrictTo.Scope.LIBRARY)
91         @Retention(AnnotationRetention.SOURCE)
92         @IntDef(CALL_TYPE_AUDIO_CALL, CALL_TYPE_VIDEO_CALL)
93         @Target(AnnotationTarget.TYPE, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY)
94         public annotation class CallType
95 
96         /**
97          * Used when answering or dialing a call to indicate that the call does not have a video
98          * component
99          */
100         public const val CALL_TYPE_AUDIO_CALL: Int = 1
101 
102         /** Indicates video transmission is supported */
103         public const val CALL_TYPE_VIDEO_CALL: Int = 2
104 
105         @RestrictTo(RestrictTo.Scope.LIBRARY)
106         @Retention(AnnotationRetention.SOURCE)
107         @IntDef(SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER, flag = true)
108         @Target(AnnotationTarget.TYPE, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
109         public annotation class CallCapability
110 
111         /**
112          * This call being created can be set to inactive (traditionally referred to as hold). This
113          * means that once a new call goes active, if the active call needs to be held in order to
114          * place or receive an incoming call, the active call will be placed on hold. otherwise, the
115          * active call may be disconnected.
116          */
117         public const val SUPPORTS_SET_INACTIVE: Int = 1 shl 1
118 
119         /**
120          * This call can be streamed from a root device to another device to continue the call
121          * without completely transferring it. The call continues to take place on the source
122          * device, however media and control are streamed to another device.
123          */
124         public const val SUPPORTS_STREAM: Int = 1 shl 2
125 
126         /** This call can be completely transferred from one endpoint to another. */
127         public const val SUPPORTS_TRANSFER: Int = 1 shl 3
128     }
129 
130     @RequiresApi(34)
toCallAttributesnull131     internal fun toCallAttributes(
132         phoneAccountHandle: PhoneAccountHandle
133     ): android.telecom.CallAttributes {
134         return CallAttributesUtils.Api34PlusImpl.toTelecomCallAttributes(
135             phoneAccountHandle,
136             direction,
137             displayName,
138             address,
139             callType,
140             callCapabilities
141         )
142     }
143 
directionToStringnull144     private fun directionToString(): String {
145         return if (direction == DIRECTION_OUTGOING) {
146             "Outgoing"
147         } else {
148             "Incoming"
149         }
150     }
151 
callTypeToStringnull152     private fun callTypeToString(): String {
153         return if (callType == CALL_TYPE_AUDIO_CALL) {
154             "Audio"
155         } else {
156             "Video"
157         }
158     }
159 
hasSupportsSetInactiveCapabilitynull160     internal fun hasSupportsSetInactiveCapability(): Boolean {
161         return Utils.hasCapability(SUPPORTS_SET_INACTIVE, callCapabilities)
162     }
163 
hasStreamCapabilitynull164     private fun hasStreamCapability(): Boolean {
165         return Utils.hasCapability(SUPPORTS_STREAM, callCapabilities)
166     }
167 
hasTransferCapabilitynull168     private fun hasTransferCapability(): Boolean {
169         return Utils.hasCapability(SUPPORTS_TRANSFER, callCapabilities)
170     }
171 
capabilitiesToStringnull172     private fun capabilitiesToString(): String {
173         val sb = StringBuilder()
174         sb.append("[")
175         if (hasSupportsSetInactiveCapability()) {
176             sb.append("SetInactive")
177         }
178         if (hasStreamCapability()) {
179             sb.append(", Stream")
180         }
181         if (hasTransferCapability()) {
182             sb.append(", Transfer")
183         }
184         sb.append("])")
185         return sb.toString()
186     }
187 
isOutgoingCallnull188     internal fun isOutgoingCall(): Boolean {
189         return direction == DIRECTION_OUTGOING
190     }
191 
isVideoCallnull192     internal fun isVideoCall(): Boolean {
193         return callType == CALL_TYPE_VIDEO_CALL
194     }
195 }
196