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