1 /*
2  * Copyright (C) 2019 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 com.android.ims.internal;
18 
19 import android.net.Uri;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.telecom.Connection;
23 import android.telecom.PhoneAccount;
24 import android.telephony.PhoneNumberUtils;
25 import android.text.TextUtils;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.telephony.PhoneConstants;
29 import com.android.telephony.Rlog;
30 
31 /**
32  * Parcelable representation of a participant's state in a conference call.
33  * @hide
34  */
35 public class ConferenceParticipant implements Parcelable {
36     private static final String TAG = "ConferenceParticipant";
37 
38     /**
39      * RFC5767 states that a SIP URI with an unknown number should use an address of
40      * {@code anonymous@anonymous.invalid}.  E.g. the host name is anonymous.invalid.
41      */
42     private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid";
43     /**
44      * The conference participant's handle (e.g., phone number).
45      */
46     private final Uri mHandle;
47 
48     /**
49      * The display name for the participant.
50      */
51     private final String mDisplayName;
52 
53     /**
54      * The endpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
55      * conference call, this is the endpoint URI for the participant on the IMS conference server.
56      */
57     private final Uri mEndpoint;
58 
59     /**
60      * The state of the participant in the conference.
61      *
62      * @see android.telecom.Connection
63      */
64     private final int mState;
65 
66     /**
67      * The connect time of the participant.
68      */
69     private long mConnectTime;
70 
71     /**
72      * The connect elapsed time of the participant.
73      */
74     private long mConnectElapsedTime;
75 
76     /**
77      * The direction of the call;
78      * {@link android.telecom.Call.Details#DIRECTION_INCOMING} for incoming calls, or
79      * {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for outgoing calls.
80      */
81     private int mCallDirection;
82 
83     /**
84      * Creates an instance of {@code ConferenceParticipant}.
85      *
86      * @param handle      The conference participant's handle (e.g., phone number).
87      * @param displayName The display name for the participant.
88      * @param endpoint    The enpoint Uri which uniquely identifies this conference participant.
89      * @param state       The state of the participant in the conference.
90      * @param callDirection The direction of the call (incoming/outgoing).
91      */
ConferenceParticipant(Uri handle, String displayName, Uri endpoint, int state, int callDirection)92     public ConferenceParticipant(Uri handle, String displayName, Uri endpoint, int state,
93             int callDirection) {
94         mHandle = handle;
95         mDisplayName = displayName;
96         mEndpoint = endpoint;
97         mState = state;
98         mCallDirection = callDirection;
99     }
100 
101     /**
102      * Responsible for creating {@code ConferenceParticipant} objects for deserialized Parcels.
103      */
104     public static final @android.annotation.NonNull Parcelable.Creator<ConferenceParticipant> CREATOR =
105             new Parcelable.Creator<ConferenceParticipant>() {
106 
107                 @Override
108                 public ConferenceParticipant createFromParcel(Parcel source) {
109                     ClassLoader classLoader = ConferenceParticipant.class.getClassLoader();
110                     Uri handle = source.readParcelable(classLoader);
111                     String displayName = source.readString();
112                     Uri endpoint = source.readParcelable(classLoader);
113                     int state = source.readInt();
114                     long connectTime = source.readLong();
115                     long elapsedRealTime = source.readLong();
116                     int callDirection = source.readInt();
117                     ConferenceParticipant participant =
118                             new ConferenceParticipant(handle, displayName, endpoint, state,
119                                     callDirection);
120                     participant.setConnectTime(connectTime);
121                     participant.setConnectElapsedTime(elapsedRealTime);
122                     return participant;
123                 }
124 
125                 @Override
126                 public ConferenceParticipant[] newArray(int size) {
127                     return new ConferenceParticipant[size];
128                 }
129             };
130 
131     @Override
describeContents()132     public int describeContents() {
133         return 0;
134     }
135 
136     /**
137      * Determines the number presentation for a conference participant.  Per RFC5767, if the host
138      * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID
139      * information for the caller, otherwise we'll assume that the URI can be shown.
140      *
141      * @return The number presentation.
142      */
143     @VisibleForTesting
getParticipantPresentation()144     public int getParticipantPresentation() {
145         Uri address = getHandle();
146         if (address == null) {
147             return PhoneConstants.PRESENTATION_RESTRICTED;
148         }
149 
150         String number = address.getSchemeSpecificPart();
151         // If no number, bail early and set restricted presentation.
152         if (TextUtils.isEmpty(number)) {
153             return PhoneConstants.PRESENTATION_RESTRICTED;
154         }
155         // Per RFC3261, the host name portion can also potentially include extra information:
156         // E.g. sip:anonymous1@anonymous.invalid;legid=1
157         // In this case, hostName will be anonymous.invalid and there is an extra parameter for
158         // legid=1.
159         // Parameters are optional, and the address (e.g. test@test.com) will always be the first
160         // part, with any parameters coming afterwards.
161         String [] hostParts = number.split("[;]");
162         String addressPart = hostParts[0];
163 
164         // Get the number portion from the address part.
165         // This will typically be formatted similar to: 6505551212@test.com
166         String [] numberParts = addressPart.split("[@]");
167 
168         // If we can't parse the host name out of the URI, then there is probably other data
169         // present, and is likely a valid SIP URI.
170         if (numberParts.length != 2) {
171             return PhoneConstants.PRESENTATION_ALLOWED;
172         }
173         String hostName = numberParts[1];
174 
175         // If the hostname portion of the SIP URI is the invalid host string, presentation is
176         // restricted.
177         if (hostName.equals(ANONYMOUS_INVALID_HOST)) {
178             return PhoneConstants.PRESENTATION_RESTRICTED;
179         }
180 
181         return PhoneConstants.PRESENTATION_ALLOWED;
182     }
183 
184     /**
185      * Writes the {@code ConferenceParticipant} to a parcel.
186      *
187      * @param dest The Parcel in which the object should be written.
188      * @param flags Additional flags about how the object should be written.
189      */
190     @Override
writeToParcel(Parcel dest, int flags)191     public void writeToParcel(Parcel dest, int flags) {
192         dest.writeParcelable(mHandle, 0);
193         dest.writeString(mDisplayName);
194         dest.writeParcelable(mEndpoint, 0);
195         dest.writeInt(mState);
196         dest.writeLong(mConnectTime);
197         dest.writeLong(mConnectElapsedTime);
198         dest.writeInt(mCallDirection);
199     }
200 
201     /**
202      * Builds a string representation of this instance.
203      *
204      * @return String representing the conference participant.
205      */
206     @Override
toString()207     public String toString() {
208         StringBuilder sb = new StringBuilder();
209         sb.append("[ConferenceParticipant Handle: ");
210         sb.append(Rlog.pii(TAG, mHandle));
211         sb.append(" DisplayName: ");
212         sb.append(Rlog.pii(TAG, mDisplayName));
213         sb.append(" Endpoint: ");
214         sb.append(Rlog.pii(TAG, mEndpoint));
215         sb.append(" State: ");
216         sb.append(Connection.stateToString(mState));
217         sb.append(" ConnectTime: ");
218         sb.append(getConnectTime());
219         sb.append(" ConnectElapsedTime: ");
220         sb.append(getConnectElapsedTime());
221         sb.append(" Direction: ");
222         sb.append(getCallDirection() == android.telecom.Call.Details.DIRECTION_INCOMING ? "Incoming" : "Outgoing");
223         sb.append("]");
224         return sb.toString();
225     }
226 
227     /**
228      * The conference participant's handle (e.g., phone number).
229      */
getHandle()230     public Uri getHandle() {
231         return mHandle;
232     }
233 
234     /**
235      * The display name for the participant.
236      */
getDisplayName()237     public String getDisplayName() {
238         return mDisplayName;
239     }
240 
241     /**
242      * The enpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
243      * conference call, this is the endpoint URI for the participant on the IMS conference server.
244      */
getEndpoint()245     public Uri getEndpoint() {
246         return mEndpoint;
247     }
248 
249     /**
250      * The state of the participant in the conference.
251      *
252      * @see android.telecom.Connection
253      */
getState()254     public int getState() {
255         return mState;
256     }
257 
258     /**
259      * The connect time of the participant to the conference.
260      */
getConnectTime()261     public long getConnectTime() {
262         return mConnectTime;
263     }
264 
setConnectTime(long connectTime)265     public void setConnectTime(long connectTime) {
266         this.mConnectTime = connectTime;
267     }
268 
269     /**
270      * The connect elapsed time of the participant to the conference.
271      */
getConnectElapsedTime()272     public long getConnectElapsedTime() {
273         return mConnectElapsedTime;
274     }
275 
setConnectElapsedTime(long connectElapsedTime)276     public void setConnectElapsedTime(long connectElapsedTime) {
277         mConnectElapsedTime = connectElapsedTime;
278     }
279 
280     /**
281      * @return The direction of the call (incoming/outgoing):
282      *         {@link android.telecom.Call.Details#DIRECTION_INCOMING} for incoming calls, or
283      *         {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for outgoing calls.
284      */
getCallDirection()285     public int getCallDirection() {
286         return mCallDirection;
287     }
288 
289     /**
290      * Sets the direction of the call.
291      * @param callDirection Whether the call is incoming or outgoing:
292      *                      {@link android.telecom.Call.Details#DIRECTION_INCOMING} for
293      *                      incoming calls, or
294      *                      {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for
295      *                      outgoing calls.
296      */
setCallDirection(int callDirection)297     public void setCallDirection(int callDirection) {
298         mCallDirection = callDirection;
299     }
300 
301     /**
302      * Attempts to build a tel: style URI from a conference participant.
303      * Conference event package data contains SIP URIs, so we try to extract the phone number and
304      * format into a typical tel: style URI.
305      *
306      * @param address The conference participant's address.
307      * @param countryIso The country ISO of the current subscription; used when formatting the
308      *                   participant phone number to E.164 format.
309      * @return The participant's address URI.
310      * @hide
311      */
312     @VisibleForTesting
getParticipantAddress(Uri address, String countryIso)313     public static Uri getParticipantAddress(Uri address, String countryIso) {
314         if (address == null) {
315             return address;
316         }
317         // Even if address is already in tel: format, still parse it and rebuild.
318         // This is to recognize tel URIs such as:
319         // tel:6505551212;phone-context=ims.mnc012.mcc034.3gppnetwork.org
320 
321         // Conference event package participants are identified using SIP URIs (see RFC3261).
322         // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
323         // Per RFC3261, the "user" can be a telephone number.
324         // For example: sip:1650555121;phone-context=blah.com@host.com
325         // In this case, the phone number is in the user field of the URI, and the parameters can be
326         // ignored.
327         //
328         // A SIP URI can also specify a phone number in a format similar to:
329         // sip:+1-212-555-1212@something.com;user=phone
330         // In this case, the phone number is again in user field and the parameters can be ignored.
331         // We can get the user field in these instances by splitting the string on the @, ;, or :
332         // and looking at the first found item.
333         String number = address.getSchemeSpecificPart();
334         if (TextUtils.isEmpty(number)) {
335             return address;
336         }
337 
338         String numberParts[] = number.split("[@;:]");
339         if (numberParts.length == 0) {
340             return address;
341         }
342         number = numberParts[0];
343 
344         // Attempt to format the number in E.164 format and use that as part of the TEL URI.
345         // RFC2806 recommends to format telephone numbers using E.164 since it is independent of
346         // how the dialing of said numbers takes place.
347         // If conversion to E.164 fails, the returned value is null.  In that case, fallback to the
348         // number which was in the CEP data.
349         String formattedNumber = null;
350         if (!TextUtils.isEmpty(countryIso)) {
351             formattedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
352         }
353 
354         return Uri.fromParts(PhoneAccount.SCHEME_TEL,
355                 formattedNumber != null ? formattedNumber : number, null);
356     }
357 }
358