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