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