• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.services.telephony;
18 
19 import com.android.internal.telephony.Phone;
20 import com.android.internal.telephony.PhoneConstants;
21 
22 import android.net.Uri;
23 import android.telecom.Connection;
24 import android.telecom.ConferenceParticipant;
25 import android.telecom.DisconnectCause;
26 import android.telecom.PhoneAccount;
27 import android.telephony.PhoneNumberUtils;
28 import android.telephony.SubscriptionInfo;
29 import android.text.TextUtils;
30 
31 /**
32  * Represents a participant in a conference call.
33  */
34 public class ConferenceParticipantConnection extends Connection {
35     /**
36      * RFC5767 states that a SIP URI with an unknown number should use an address of
37      * {@code anonymous@anonymous.invalid}.  E.g. the host name is anonymous.invalid.
38      */
39     private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid";
40 
41     /**
42      * The user entity URI For the conference participant.
43      */
44     private final Uri mUserEntity;
45 
46     /**
47      * The endpoint URI For the conference participant.
48      */
49     private final Uri mEndpoint;
50 
51     /**
52      * The connection which owns this participant.
53      */
54     private final com.android.internal.telephony.Connection mParentConnection;
55 
56     /**
57      * Creates a new instance.
58      *
59      * @param participant The conference participant to create the instance for.
60      */
ConferenceParticipantConnection( com.android.internal.telephony.Connection parentConnection, ConferenceParticipant participant)61     public ConferenceParticipantConnection(
62             com.android.internal.telephony.Connection parentConnection,
63             ConferenceParticipant participant) {
64 
65         mParentConnection = parentConnection;
66 
67         int presentation = getParticipantPresentation(participant);
68         Uri address;
69         if (presentation != PhoneConstants.PRESENTATION_ALLOWED) {
70             address = null;
71         } else {
72             String countryIso = getCountryIso(parentConnection.getCall().getPhone());
73             address = getParticipantAddress(participant, countryIso);
74         }
75         setAddress(address, presentation);
76         setCallerDisplayName(participant.getDisplayName(), presentation);
77 
78         mUserEntity = participant.getHandle();
79         mEndpoint = participant.getEndpoint();
80 
81         setCapabilities();
82     }
83 
84     /**
85      * Changes the state of the conference participant.
86      *
87      * @param newState The new state.
88      */
updateState(int newState)89     public void updateState(int newState) {
90         Log.v(this, "updateState endPoint: %s state: %s", Log.pii(mEndpoint),
91                 Connection.stateToString(newState));
92         if (newState == getState()) {
93             return;
94         }
95 
96         switch (newState) {
97             case STATE_INITIALIZING:
98                 setInitializing();
99                 break;
100             case STATE_RINGING:
101                 setRinging();
102                 break;
103             case STATE_DIALING:
104                 setDialing();
105                 break;
106             case STATE_HOLDING:
107                 setOnHold();
108                 break;
109             case STATE_ACTIVE:
110                 setActive();
111                 break;
112             case STATE_DISCONNECTED:
113                 setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
114                 destroy();
115                 break;
116             default:
117                 setActive();
118         }
119     }
120 
121     /**
122      * Disconnects the current {@code ConferenceParticipantConnection} from the conference.
123      * <p>
124      * Sends a participant disconnect signal to the associated parent connection.  The participant
125      * connection is not disconnected and cleaned up here.  On successful disconnection of the
126      * participant, the conference server will send an update to the conference controller
127      * indicating the disconnection was successful.
128      */
129     @Override
onDisconnect()130     public void onDisconnect() {
131         mParentConnection.onDisconnectConferenceParticipant(mUserEntity);
132     }
133 
134     /**
135      * Retrieves the user handle for this connection.
136      *
137      * @return The userEntity.
138      */
getUserEntity()139     public Uri getUserEntity() {
140         return mUserEntity;
141     }
142 
143     /**
144      * Retrieves the endpoint for this connection.
145      *
146      * @return The endpoint.
147      */
getEndpoint()148     public Uri getEndpoint() {
149         return mEndpoint;
150     }
151 
152     /**
153      * Configures the capabilities applicable to this connection.  A
154      * conference participant can only be disconnected from a conference since there is not
155      * actual connection to the participant which could be split from the conference.
156      */
setCapabilities()157     private void setCapabilities() {
158         int capabilities = CAPABILITY_DISCONNECT_FROM_CONFERENCE;
159         setConnectionCapabilities(capabilities);
160     }
161 
162     /**
163      * Determines the number presentation for a conference participant.  Per RFC5767, if the host
164      * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID
165      * information for the caller, otherwise we'll assume that the URI can be shown.
166      *
167      * @param participant The conference participant.
168      * @return The number presentation.
169      */
getParticipantPresentation(ConferenceParticipant participant)170     private int getParticipantPresentation(ConferenceParticipant participant) {
171         Uri address = participant.getHandle();
172         if (address == null) {
173             return PhoneConstants.PRESENTATION_RESTRICTED;
174         }
175 
176         String number = address.getSchemeSpecificPart();
177         // If no number, bail early and set restricted presentation.
178         if (TextUtils.isEmpty(number)) {
179             return PhoneConstants.PRESENTATION_RESTRICTED;
180         }
181         // Per RFC3261, the host name portion can also potentially include extra information:
182         // E.g. sip:anonymous1@anonymous.invalid;legid=1
183         // In this case, hostName will be anonymous.invalid and there is an extra parameter for
184         // legid=1.
185         // Parameters are optional, and the address (e.g. test@test.com) will always be the first
186         // part, with any parameters coming afterwards.
187         String hostParts[] = number.split("[;]");
188         String addressPart = hostParts[0];
189 
190         // Get the number portion from the address part.
191         // This will typically be formatted similar to: 6505551212@test.com
192         String numberParts[] = addressPart.split("[@]");
193 
194         // If we can't parse the host name out of the URI, then there is probably other data
195         // present, and is likely a valid SIP URI.
196         if (numberParts.length != 2) {
197             return PhoneConstants.PRESENTATION_ALLOWED;
198         }
199         String hostName = numberParts[1];
200 
201         // If the hostname portion of the SIP URI is the invalid host string, presentation is
202         // restricted.
203         if (hostName.equals(ANONYMOUS_INVALID_HOST)) {
204             return PhoneConstants.PRESENTATION_RESTRICTED;
205         }
206 
207         return PhoneConstants.PRESENTATION_ALLOWED;
208     }
209 
210     /**
211      * Attempts to build a tel: style URI from a conference participant.
212      * Conference event package data contains SIP URIs, so we try to extract the phone number and
213      * format into a typical tel: style URI.
214      *
215      * @param participant The conference participant.
216      * @param countryIso The country ISO of the current subscription; used when formatting the
217      *                   participant phone number to E.164 format.
218      * @return The participant's address URI.
219      */
getParticipantAddress(ConferenceParticipant participant, String countryIso)220     private Uri getParticipantAddress(ConferenceParticipant participant, String countryIso) {
221         Uri address = participant.getHandle();
222         if (address == null) {
223             return address;
224         }
225 
226         // If the participant's address is already a TEL scheme, just return it as is.
227         if (PhoneAccount.SCHEME_TEL.equals(address.getScheme())) {
228             return address;
229         }
230 
231         // Conference event package participants are identified using SIP URIs (see RFC3261).
232         // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
233         // Per RFC3261, the "user" can be a telephone number.
234         // For example: sip:1650555121;phone-context=blah.com@host.com
235         // In this case, the phone number is in the user field of the URI, and the parameters can be
236         // ignored.
237         //
238         // A SIP URI can also specify a phone number in a format similar to:
239         // sip:+1-212-555-1212@something.com;user=phone
240         // In this case, the phone number is again in user field and the parameters can be ignored.
241         // We can get the user field in these instances by splitting the string on the @, ;, or :
242         // and looking at the first found item.
243         String number = address.getSchemeSpecificPart();
244         if (TextUtils.isEmpty(number)) {
245             return address;
246         }
247 
248         String numberParts[] = number.split("[@;:]");
249         if (numberParts.length == 0) {
250             return address;
251         }
252         number = numberParts[0];
253 
254         // Attempt to format the number in E.164 format and use that as part of the TEL URI.
255         // RFC2806 recommends to format telephone numbers using E.164 since it is independent of
256         // how the dialing of said numbers takes place.
257         // If conversion to E.164 fails, the returned value is null.  In that case, fallback to the
258         // number which was in the CEP data.
259         String formattedNumber = null;
260         if (!TextUtils.isEmpty(countryIso)) {
261             formattedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
262         }
263 
264         return Uri.fromParts(PhoneAccount.SCHEME_TEL,
265                 formattedNumber != null ? formattedNumber : number, null);
266     }
267 
268     /**
269      * Given a {@link Phone} instance, determines the country ISO associated with the phone's
270      * subscription.
271      *
272      * @param phone The phone instance.
273      * @return The country ISO.
274      */
getCountryIso(Phone phone)275     private String getCountryIso(Phone phone) {
276         if (phone == null) {
277             return null;
278         }
279 
280         int subId = phone.getSubId();
281 
282         SubscriptionInfo subInfo = TelecomAccountRegistry.getInstance(null).
283                 getSubscriptionManager().getActiveSubscriptionInfo(subId);
284 
285         if (subInfo == null) {
286             return null;
287         }
288         // The SubscriptionInfo reports ISO country codes in lower case.  Convert to upper case,
289         // since ultimately we use this ISO when formatting the CEP phone number, and the phone
290         // number formatting library expects uppercase ISO country codes.
291         return subInfo.getCountryIso().toUpperCase();
292     }
293 
294     /**
295      * Builds a string representation of this conference participant connection.
296      *
297      * @return String representation of connection.
298      */
299     @Override
toString()300     public String toString() {
301         StringBuilder sb = new StringBuilder();
302         sb.append("[ConferenceParticipantConnection objId:");
303         sb.append(System.identityHashCode(this));
304         sb.append(" endPoint:");
305         sb.append(Log.pii(mEndpoint));
306         sb.append(" address:");
307         sb.append(Log.pii(getAddress()));
308         sb.append(" addressPresentation:");
309         sb.append(getAddressPresentation());
310         sb.append(" parentConnection:");
311         sb.append(Log.pii(mParentConnection.getAddress()));
312         sb.append(" state:");
313         sb.append(Connection.stateToString(getState()));
314         sb.append("]");
315 
316         return sb.toString();
317     }
318 }
319