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