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 android.content.Context;
     20 import android.graphics.drawable.Icon;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.PersistableBundle;
     24 import android.telecom.Conference;
     25 import android.telecom.ConferenceParticipant;
     26 import android.telecom.Connection;
     27 import android.telecom.Connection.VideoProvider;
     28 import android.telecom.DisconnectCause;
     29 import android.telecom.Log;
     30 import android.telecom.PhoneAccountHandle;
     31 import android.telecom.StatusHints;
     32 import android.telecom.VideoProfile;
     33 import android.telephony.CarrierConfigManager;
     34 import android.telephony.PhoneNumberUtils;
     35 import android.util.Pair;
     36 
     37 import com.android.internal.telephony.Call;
     38 import com.android.internal.telephony.CallStateException;
     39 import com.android.internal.telephony.Phone;
     40 import com.android.internal.telephony.PhoneConstants;
     41 import com.android.phone.PhoneGlobals;
     42 import com.android.phone.PhoneUtils;
     43 import com.android.phone.R;
     44 
     45 import java.util.ArrayList;
     46 import java.util.Arrays;
     47 import java.util.HashMap;
     48 import java.util.HashSet;
     49 import java.util.Iterator;
     50 import java.util.List;
     51 import java.util.Map;
     52 
     53 /**
     54  * Represents an IMS conference call.
     55  * <p>
     56  * An IMS conference call consists of a conference host connection and potentially a list of
     57  * conference participants.  The conference host connection represents the radio connection to the
     58  * IMS conference server.  Since it is not a connection to any one individual, it is not represented
     59  * in Telecom/InCall as a call.  The conference participant information is received via the host
     60  * connection via a conference event package.  Conference participant connections do not represent
     61  * actual radio connections to the participants; they act as a virtual representation of the
     62  * participant, keyed by a unique endpoint {@link android.net.Uri}.
     63  * <p>
     64  * The {@link ImsConference} listens for conference event package data received via the host
     65  * connection and is responsible for managing the conference participant connections which represent
     66  * the participants.
     67  */
     68 public class ImsConference extends Conference implements Holdable {
     69 
     70     /**
     71      * Listener used to respond to changes to conference participants.  At the conference level we
     72      * are most concerned with handling destruction of a conference participant.
     73      */
     74     private final Connection.Listener mParticipantListener = new Connection.Listener() {
     75         /**
     76          * Participant has been destroyed.  Remove it from the conference.
     77          *
     78          * @param connection The participant which was destroyed.
     79          */
     80         @Override
     81         public void onDestroyed(Connection connection) {
     82             ConferenceParticipantConnection participant =
     83                     (ConferenceParticipantConnection) connection;
     84             removeConferenceParticipant(participant);
     85             updateManageConference();
     86         }
     87 
     88     };
     89 
     90     /**
     91      * Listener used to respond to changes to the underlying radio connection for the conference
     92      * host connection.  Used to respond to SRVCC changes.
     93      */
     94     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
     95             new TelephonyConnection.TelephonyConnectionListener() {
     96 
     97         @Override
     98         public void onOriginalConnectionConfigured(TelephonyConnection c) {
     99             if (c == mConferenceHost) {
    100                handleOriginalConnectionChange();
    101             }
    102         }
    103     };
    104 
    105     /**
    106      * Listener used to respond to changes to the connection to the IMS conference server.
    107      */
    108     private final android.telecom.Connection.Listener mConferenceHostListener =
    109             new android.telecom.Connection.Listener() {
    110 
    111         /**
    112          * Updates the state of the conference based on the new state of the host.
    113          *
    114          * @param c The host connection.
    115          * @param state The new state
    116          */
    117         @Override
    118         public void onStateChanged(android.telecom.Connection c, int state) {
    119             setState(state);
    120         }
    121 
    122         /**
    123          * Disconnects the conference when its host connection disconnects.
    124          *
    125          * @param c The host connection.
    126          * @param disconnectCause The host connection disconnect cause.
    127          */
    128         @Override
    129         public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) {
    130             setDisconnected(disconnectCause);
    131         }
    132 
    133         /**
    134          * Handles changes to conference participant data as reported by the conference host
    135          * connection.
    136          *
    137          * @param c The connection.
    138          * @param participants The participant information.
    139          */
    140         @Override
    141         public void onConferenceParticipantsChanged(android.telecom.Connection c,
    142                 List<ConferenceParticipant> participants) {
    143 
    144             if (c == null || participants == null) {
    145                 return;
    146             }
    147             Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
    148             TelephonyConnection telephonyConnection = (TelephonyConnection) c;
    149             handleConferenceParticipantsUpdate(telephonyConnection, participants);
    150         }
    151 
    152         @Override
    153         public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
    154             Log.d(this, "onVideoStateChanged video state %d", videoState);
    155             setVideoState(c, videoState);
    156         }
    157 
    158         @Override
    159         public void onVideoProviderChanged(android.telecom.Connection c,
    160                 Connection.VideoProvider videoProvider) {
    161             Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
    162                     videoProvider);
    163             setVideoProvider(c, videoProvider);
    164         }
    165 
    166         @Override
    167         public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
    168             Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s," +
    169                     " connectionCapabilities: %s", c, connectionCapabilities);
    170             int capabilites = ImsConference.this.getConnectionCapabilities();
    171             boolean isVideoConferencingSupported = mConferenceHost == null ? false :
    172                     mConferenceHost.isCarrierVideoConferencingSupported();
    173             setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities,
    174                     isVideoConferencingSupported));
    175         }
    176 
    177         @Override
    178         public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
    179             Log.d(this, "onConnectionPropertiesChanged: Connection: %s," +
    180                     " connectionProperties: %s", c, connectionProperties);
    181             int properties = ImsConference.this.getConnectionProperties();
    182             setConnectionProperties(applyHostProperties(properties, connectionProperties));
    183         }
    184 
    185         @Override
    186         public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
    187             Log.v(this, "onStatusHintsChanged");
    188             updateStatusHints();
    189         }
    190 
    191         @Override
    192         public void onExtrasChanged(Connection c, Bundle extras) {
    193             Log.v(this, "onExtrasChanged: c=" + c + " Extras=" + extras);
    194             putExtras(extras);
    195         }
    196 
    197         @Override
    198         public void onExtrasRemoved(Connection c, List<String> keys) {
    199             Log.v(this, "onExtrasRemoved: c=" + c + " key=" + keys);
    200             removeExtras(keys);
    201         }
    202     };
    203 
    204     /**
    205      * The telephony connection service; used to add new participant connections to Telecom.
    206      */
    207     private TelephonyConnectionServiceProxy mTelephonyConnectionService;
    208 
    209     /**
    210      * The connection to the conference server which is hosting the conference.
    211      */
    212     private TelephonyConnection mConferenceHost;
    213 
    214     /**
    215      * The PhoneAccountHandle of the conference host.
    216      */
    217     private PhoneAccountHandle mConferenceHostPhoneAccountHandle;
    218 
    219     /**
    220      * The address of the conference host.
    221      */
    222     private Uri[] mConferenceHostAddress;
    223 
    224     private TelecomAccountRegistry mTelecomAccountRegistry;
    225 
    226     /**
    227      * The known conference participant connections.  The HashMap is keyed by a Pair containing
    228      * the handle and endpoint Uris.
    229      * Access to the hashmap is protected by the {@link #mUpdateSyncRoot}.
    230      */
    231     private final HashMap<Pair<Uri, Uri>, ConferenceParticipantConnection>
    232             mConferenceParticipantConnections = new HashMap<>();
    233 
    234     /**
    235      * Sychronization root used to ensure that updates to the
    236      * {@link #mConferenceParticipantConnections} happen atomically are are not interleaved across
    237      * threads.  There are some instances where the network will send conference event package
    238      * data closely spaced.  If that happens, it is possible that the interleaving of the update
    239      * will cause duplicate participant info to be added.
    240      */
    241     private final Object mUpdateSyncRoot = new Object();
    242 
    243     private boolean mIsHoldable;
    244 
    245     public void updateConferenceParticipantsAfterCreation() {
    246         if (mConferenceHost != null) {
    247             Log.v(this, "updateConferenceStateAfterCreation :: process participant update");
    248             handleConferenceParticipantsUpdate(mConferenceHost,
    249                     mConferenceHost.getConferenceParticipants());
    250         } else {
    251             Log.v(this, "updateConferenceStateAfterCreation :: null mConferenceHost");
    252         }
    253     }
    254 
    255     /**
    256      * Initializes a new {@link ImsConference}.
    257      *
    258      * @param telephonyConnectionService The connection service responsible for adding new
    259      *                                   conferene participants.
    260      * @param conferenceHost The telephony connection hosting the conference.
    261      * @param phoneAccountHandle The phone account handle associated with the conference.
    262      */
    263     public ImsConference(TelecomAccountRegistry telecomAccountRegistry,
    264                          TelephonyConnectionServiceProxy telephonyConnectionService,
    265             TelephonyConnection conferenceHost, PhoneAccountHandle phoneAccountHandle) {
    266 
    267         super(phoneAccountHandle);
    268 
    269         mTelecomAccountRegistry = telecomAccountRegistry;
    270 
    271         // Specify the connection time of the conference to be the connection time of the original
    272         // connection.
    273         long connectTime = conferenceHost.getOriginalConnection().getConnectTime();
    274         long connectElapsedTime = conferenceHost.getOriginalConnection().getConnectTimeReal();
    275         setConnectionTime(connectTime);
    276         setConnectionStartElapsedRealTime(connectElapsedTime);
    277         // Set the connectTime in the connection as well.
    278         conferenceHost.setConnectTimeMillis(connectTime);
    279         conferenceHost.setConnectionStartElapsedRealTime(connectElapsedTime);
    280 
    281         mTelephonyConnectionService = telephonyConnectionService;
    282         setConferenceHost(conferenceHost);
    283 
    284         int capabilities = Connection.CAPABILITY_MUTE |
    285                 Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
    286         if (canHoldImsCalls()) {
    287             capabilities |= Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD;
    288             mIsHoldable = true;
    289         }
    290         capabilities = applyHostCapabilities(capabilities,
    291                 mConferenceHost.getConnectionCapabilities(),
    292                 mConferenceHost.isCarrierVideoConferencingSupported());
    293         setConnectionCapabilities(capabilities);
    294 
    295     }
    296 
    297     /**
    298      * Transfers capabilities from the conference host to the conference itself.
    299      *
    300      * @param conferenceCapabilities The current conference capabilities.
    301      * @param capabilities The new conference host capabilities.
    302      * @param isVideoConferencingSupported Whether video conferencing is supported.
    303      * @return The merged capabilities to be applied to the conference.
    304      */
    305     private int applyHostCapabilities(int conferenceCapabilities, int capabilities,
    306             boolean isVideoConferencingSupported) {
    307 
    308         conferenceCapabilities = changeBitmask(conferenceCapabilities,
    309                     Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
    310                     can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
    311 
    312         if (isVideoConferencingSupported) {
    313             conferenceCapabilities = changeBitmask(conferenceCapabilities,
    314                     Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
    315                     can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
    316             conferenceCapabilities = changeBitmask(conferenceCapabilities,
    317                     Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
    318                     can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO));
    319         } else {
    320             // If video conferencing is not supported, explicitly turn off the remote video
    321             // capability and the ability to upgrade to video.
    322             Log.v(this, "applyHostCapabilities : video conferencing not supported");
    323             conferenceCapabilities = changeBitmask(conferenceCapabilities,
    324                     Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, false);
    325             conferenceCapabilities = changeBitmask(conferenceCapabilities,
    326                     Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, false);
    327         }
    328 
    329         conferenceCapabilities = changeBitmask(conferenceCapabilities,
    330                 Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
    331                 can(capabilities, Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO));
    332 
    333         conferenceCapabilities = changeBitmask(conferenceCapabilities,
    334                 Connection.CAPABILITY_CAN_PAUSE_VIDEO,
    335                 mConferenceHost.getVideoPauseSupported() && isVideoCapable());
    336 
    337         return conferenceCapabilities;
    338     }
    339 
    340     /**
    341      * Transfers properties from the conference host to the conference itself.
    342      *
    343      * @param conferenceProperties The current conference properties.
    344      * @param properties The new conference host properties.
    345      * @return The merged properties to be applied to the conference.
    346      */
    347     private int applyHostProperties(int conferenceProperties, int properties) {
    348         conferenceProperties = changeBitmask(conferenceProperties,
    349                 Connection.PROPERTY_HIGH_DEF_AUDIO,
    350                 can(properties, Connection.PROPERTY_HIGH_DEF_AUDIO));
    351 
    352         conferenceProperties = changeBitmask(conferenceProperties,
    353                 Connection.PROPERTY_WIFI,
    354                 can(properties, Connection.PROPERTY_WIFI));
    355 
    356         conferenceProperties = changeBitmask(conferenceProperties,
    357                 Connection.PROPERTY_IS_EXTERNAL_CALL,
    358                 can(properties, Connection.PROPERTY_IS_EXTERNAL_CALL));
    359 
    360         return conferenceProperties;
    361     }
    362 
    363     /**
    364      * Not used by the IMS conference controller.
    365      *
    366      * @return {@code Null}.
    367      */
    368     @Override
    369     public android.telecom.Connection getPrimaryConnection() {
    370         return null;
    371     }
    372 
    373     /**
    374      * Returns VideoProvider of the conference. This can be null.
    375      *
    376      * @hide
    377      */
    378     @Override
    379     public VideoProvider getVideoProvider() {
    380         if (mConferenceHost != null) {
    381             return mConferenceHost.getVideoProvider();
    382         }
    383         return null;
    384     }
    385 
    386     /**
    387      * Returns video state of conference
    388      *
    389      * @hide
    390      */
    391     @Override
    392     public int getVideoState() {
    393         if (mConferenceHost != null) {
    394             return mConferenceHost.getVideoState();
    395         }
    396         return VideoProfile.STATE_AUDIO_ONLY;
    397     }
    398 
    399     /**
    400      * Invoked when the Conference and all its {@link Connection}s should be disconnected.
    401      * <p>
    402      * Hangs up the call via the conference host connection.  When the host connection has been
    403      * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
    404      * {@code onDestroyed} event, which triggers the conference participant connections to be
    405      * disconnected.
    406      */
    407     @Override
    408     public void onDisconnect() {
    409         Log.v(this, "onDisconnect: hanging up conference host.");
    410         if (mConferenceHost == null) {
    411             return;
    412         }
    413 
    414         disconnectConferenceParticipants();
    415 
    416         Call call = mConferenceHost.getCall();
    417         if (call != null) {
    418             try {
    419                 call.hangup();
    420             } catch (CallStateException e) {
    421                 Log.e(this, e, "Exception thrown trying to hangup conference");
    422             }
    423         }
    424     }
    425 
    426     /**
    427      * Invoked when the specified {@link android.telecom.Connection} should be separated from the
    428      * conference call.
    429      * <p>
    430      * IMS does not support separating connections from the conference.
    431      *
    432      * @param connection The connection to separate.
    433      */
    434     @Override
    435     public void onSeparate(android.telecom.Connection connection) {
    436         Log.wtf(this, "Cannot separate connections from an IMS conference.");
    437     }
    438 
    439     /**
    440      * Invoked when the specified {@link android.telecom.Connection} should be merged into the
    441      * conference call.
    442      *
    443      * @param connection The {@code Connection} to merge.
    444      */
    445     @Override
    446     public void onMerge(android.telecom.Connection connection) {
    447         try {
    448             Phone phone = mConferenceHost.getPhone();
    449             if (phone != null) {
    450                 phone.conference();
    451             }
    452         } catch (CallStateException e) {
    453             Log.e(this, e, "Exception thrown trying to merge call into a conference");
    454         }
    455     }
    456 
    457     /**
    458      * Invoked when the conference should be put on hold.
    459      */
    460     @Override
    461     public void onHold() {
    462         if (mConferenceHost == null) {
    463             return;
    464         }
    465         mConferenceHost.performHold();
    466     }
    467 
    468     /**
    469      * Invoked when the conference should be moved from hold to active.
    470      */
    471     @Override
    472     public void onUnhold() {
    473         if (mConferenceHost == null) {
    474             return;
    475         }
    476         mConferenceHost.performUnhold();
    477     }
    478 
    479     /**
    480      * Invoked to play a DTMF tone.
    481      *
    482      * @param c A DTMF character.
    483      */
    484     @Override
    485     public void onPlayDtmfTone(char c) {
    486         if (mConferenceHost == null) {
    487             return;
    488         }
    489         mConferenceHost.onPlayDtmfTone(c);
    490     }
    491 
    492     /**
    493      * Invoked to stop playing a DTMF tone.
    494      */
    495     @Override
    496     public void onStopDtmfTone() {
    497         if (mConferenceHost == null) {
    498             return;
    499         }
    500         mConferenceHost.onStopDtmfTone();
    501     }
    502 
    503     /**
    504      * Handles the addition of connections to the {@link ImsConference}.  The
    505      * {@link ImsConferenceController} does not add connections to the conference.
    506      *
    507      * @param connection The newly added connection.
    508      */
    509     @Override
    510     public void onConnectionAdded(android.telecom.Connection connection) {
    511         // No-op
    512     }
    513 
    514     @Override
    515     public void setHoldable(boolean isHoldable) {
    516         mIsHoldable = isHoldable;
    517         if (!mIsHoldable) {
    518             removeCapability(Connection.CAPABILITY_HOLD);
    519         } else {
    520             addCapability(Connection.CAPABILITY_HOLD);
    521         }
    522     }
    523 
    524     @Override
    525     public boolean isChildHoldable() {
    526         // The conference should not be a child of other conference.
    527         return false;
    528     }
    529 
    530     /**
    531      * Changes a bit-mask to add or remove a bit-field.
    532      *
    533      * @param bitmask The bit-mask.
    534      * @param bitfield The bit-field to change.
    535      * @param enabled Whether the bit-field should be set or removed.
    536      * @return The bit-mask with the bit-field changed.
    537      */
    538     private int changeBitmask(int bitmask, int bitfield, boolean enabled) {
    539         if (enabled) {
    540             return bitmask | bitfield;
    541         } else {
    542             return bitmask & ~bitfield;
    543         }
    544     }
    545 
    546     /**
    547      * Determines if this conference is hosted on the current device or the peer device.
    548      *
    549      * @return {@code true} if this conference is hosted on the current device, {@code false} if it
    550      *      is hosted on the peer device.
    551      */
    552     public boolean isConferenceHost() {
    553         if (mConferenceHost == null) {
    554             return false;
    555         }
    556         com.android.internal.telephony.Connection originalConnection =
    557                 mConferenceHost.getOriginalConnection();
    558 
    559         return originalConnection != null && originalConnection.isMultiparty() &&
    560                 originalConnection.isConferenceHost();
    561     }
    562 
    563     /**
    564      * Updates the manage conference capability of the conference.  Where there are one or more
    565      * conference event package participants, the conference management is permitted.  Where there
    566      * are no conference event package participants, conference management is not permitted.
    567      * <p>
    568      * Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure
    569      * that the conference is represented appropriately on Bluetooth devices.
    570      */
    571     private void updateManageConference() {
    572         boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
    573         boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
    574         Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
    575                 canManageConference ? "Y" : "N");
    576 
    577         if (couldManageConference != canManageConference) {
    578             int capabilities = getConnectionCapabilities();
    579 
    580             if (canManageConference) {
    581                 capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
    582                 capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
    583             } else {
    584                 capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
    585                 capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
    586             }
    587 
    588             setConnectionCapabilities(capabilities);
    589         }
    590     }
    591 
    592     /**
    593      * Sets the connection hosting the conference and registers for callbacks.
    594      *
    595      * @param conferenceHost The connection hosting the conference.
    596      */
    597     private void setConferenceHost(TelephonyConnection conferenceHost) {
    598         if (Log.VERBOSE) {
    599             Log.v(this, "setConferenceHost " + conferenceHost);
    600         }
    601 
    602         mConferenceHost = conferenceHost;
    603 
    604         // Attempt to get the conference host's address (e.g. the host's own phone number).
    605         // We need to look at the default phone for the ImsPhone when creating the phone account
    606         // for the
    607         if (mConferenceHost.getPhone() != null &&
    608                 mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
    609             // Look up the conference host's address; we need this later for filtering out the
    610             // conference host in conference event package data.
    611             Phone imsPhone = mConferenceHost.getPhone();
    612             mConferenceHostPhoneAccountHandle =
    613                     PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
    614             Uri hostAddress = mTelecomAccountRegistry.getAddress(mConferenceHostPhoneAccountHandle);
    615 
    616             ArrayList<Uri> hostAddresses = new ArrayList<>();
    617 
    618             // add address from TelecomAccountRegistry
    619             if (hostAddress != null) {
    620                 hostAddresses.add(hostAddress);
    621             }
    622 
    623             // add addresses from phone
    624             if (imsPhone.getCurrentSubscriberUris() != null) {
    625                 hostAddresses.addAll(
    626                         new ArrayList<>(Arrays.asList(imsPhone.getCurrentSubscriberUris())));
    627             }
    628 
    629             mConferenceHostAddress = new Uri[hostAddresses.size()];
    630             mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress);
    631         }
    632 
    633         mConferenceHost.addConnectionListener(mConferenceHostListener);
    634         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
    635         setConnectionCapabilities(applyHostCapabilities(getConnectionCapabilities(),
    636                 mConferenceHost.getConnectionCapabilities(),
    637                 mConferenceHost.isCarrierVideoConferencingSupported()));
    638         setConnectionProperties(applyHostProperties(getConnectionProperties(),
    639                 mConferenceHost.getConnectionProperties()));
    640 
    641         setState(mConferenceHost.getState());
    642         updateStatusHints();
    643     }
    644 
    645     /**
    646      * Handles state changes for conference participant(s).  The participants data passed in
    647      *
    648      * @param parent The connection which was notified of the conference participant.
    649      * @param participants The conference participant information.
    650      */
    651     private void handleConferenceParticipantsUpdate(
    652             TelephonyConnection parent, List<ConferenceParticipant> participants) {
    653 
    654         if (participants == null) {
    655             return;
    656         }
    657 
    658         if (parent != null && !parent.isManageImsConferenceCallSupported()) {
    659             Log.i(this, "handleConferenceParticipantsUpdate: manage conference is disallowed");
    660             return;
    661         }
    662 
    663         Log.i(this, "handleConferenceParticipantsUpdate: size=%d", participants.size());
    664 
    665         // Perform the update in a synchronized manner.  It is possible for the IMS framework to
    666         // trigger two onConferenceParticipantsChanged callbacks in quick succession.  If the first
    667         // update adds new participants, and the second does something like update the status of one
    668         // of the participants, we can get into a situation where the participant is added twice.
    669         synchronized (mUpdateSyncRoot) {
    670             boolean newParticipantsAdded = false;
    671             boolean oldParticipantsRemoved = false;
    672             ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
    673             HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size());
    674 
    675             // Add any new participants and update existing.
    676             for (ConferenceParticipant participant : participants) {
    677                 Pair<Uri,Uri> userEntity = new Pair<>(participant.getHandle(),
    678                         participant.getEndpoint());
    679 
    680                 participantUserEntities.add(userEntity);
    681                 if (!mConferenceParticipantConnections.containsKey(userEntity)) {
    682                     // Some carriers will also include the conference host in the CEP.  We will
    683                     // filter that out here.
    684                     if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) {
    685                         createConferenceParticipantConnection(parent, participant);
    686                         newParticipants.add(participant);
    687                         newParticipantsAdded = true;
    688                     }
    689                 } else {
    690                     ConferenceParticipantConnection connection =
    691                             mConferenceParticipantConnections.get(userEntity);
    692                     Log.i(this, "handleConferenceParticipantsUpdate: updateState, participant = %s",
    693                             participant);
    694                     connection.updateState(participant.getState());
    695                 }
    696             }
    697 
    698             // Set state of new participants.
    699             if (newParticipantsAdded) {
    700                 // Set the state of the new participants at once and add to the conference
    701                 for (ConferenceParticipant newParticipant : newParticipants) {
    702                     ConferenceParticipantConnection connection =
    703                             mConferenceParticipantConnections.get(new Pair<>(
    704                                     newParticipant.getHandle(),
    705                                     newParticipant.getEndpoint()));
    706                     connection.updateState(newParticipant.getState());
    707                 }
    708             }
    709 
    710             // Finally, remove any participants from the conference that no longer exist in the
    711             // conference event package data.
    712             Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator =
    713                     mConferenceParticipantConnections.entrySet().iterator();
    714             while (entryIterator.hasNext()) {
    715                 Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection> entry =
    716                         entryIterator.next();
    717 
    718                 if (!participantUserEntities.contains(entry.getKey())) {
    719                     ConferenceParticipantConnection participant = entry.getValue();
    720                     participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
    721                     participant.removeConnectionListener(mParticipantListener);
    722                     mTelephonyConnectionService.removeConnection(participant);
    723                     removeConnection(participant);
    724                     entryIterator.remove();
    725                     oldParticipantsRemoved = true;
    726                 }
    727             }
    728 
    729             // If new participants were added or old ones were removed, we need to ensure the state
    730             // of the manage conference capability is updated.
    731             if (newParticipantsAdded || oldParticipantsRemoved) {
    732                 updateManageConference();
    733             }
    734         }
    735     }
    736 
    737     /**
    738      * Creates a new {@link ConferenceParticipantConnection} to represent a
    739      * {@link ConferenceParticipant}.
    740      * <p>
    741      * The new connection is added to the conference controller and connection service.
    742      *
    743      * @param parent The connection which was notified of the participant change (e.g. the
    744      *                         parent connection).
    745      * @param participant The conference participant information.
    746      */
    747     private void createConferenceParticipantConnection(
    748             TelephonyConnection parent, ConferenceParticipant participant) {
    749 
    750         // Create and add the new connection in holding state so that it does not become the
    751         // active call.
    752         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
    753                 parent.getOriginalConnection(), participant);
    754         connection.addConnectionListener(mParticipantListener);
    755         connection.setConnectTimeMillis(parent.getConnectTimeMillis());
    756 
    757         Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s",
    758                 participant, connection);
    759 
    760         synchronized(mUpdateSyncRoot) {
    761             mConferenceParticipantConnections.put(new Pair<>(participant.getHandle(),
    762                     participant.getEndpoint()), connection);
    763         }
    764 
    765         mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
    766                 connection, this);
    767         addConnection(connection);
    768     }
    769 
    770     /**
    771      * Removes a conference participant from the conference.
    772      *
    773      * @param participant The participant to remove.
    774      */
    775     private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
    776         Log.i(this, "removeConferenceParticipant: %s", participant);
    777 
    778         participant.removeConnectionListener(mParticipantListener);
    779         synchronized(mUpdateSyncRoot) {
    780             mConferenceParticipantConnections.remove(participant.getUserEntity());
    781         }
    782         mTelephonyConnectionService.removeConnection(participant);
    783     }
    784 
    785     /**
    786      * Disconnects all conference participants from the conference.
    787      */
    788     private void disconnectConferenceParticipants() {
    789         Log.v(this, "disconnectConferenceParticipants");
    790 
    791         synchronized(mUpdateSyncRoot) {
    792             for (ConferenceParticipantConnection connection :
    793                     mConferenceParticipantConnections.values()) {
    794 
    795                 connection.removeConnectionListener(mParticipantListener);
    796                 // Mark disconnect cause as cancelled to ensure that the call is not logged in the
    797                 // call log.
    798                 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
    799                 mTelephonyConnectionService.removeConnection(connection);
    800                 connection.destroy();
    801             }
    802             mConferenceParticipantConnections.clear();
    803         }
    804     }
    805 
    806     /**
    807      * Determines if the passed in participant handle is the same as the conference host's handle.
    808      * Starts with a simple equality check.  However, the handles from a conference event package
    809      * will be a SIP uri, so we need to pull that apart to look for the participant's phone number.
    810      *
    811      * @param hostHandles The handle(s) of the connection hosting the conference.
    812      * @param handle The handle of the conference participant.
    813      * @return {@code true} if the host's handle matches the participant's handle, {@code false}
    814      *      otherwise.
    815      */
    816     private boolean isParticipantHost(Uri[] hostHandles, Uri handle) {
    817         // If there is no host handle or no participant handle, bail early.
    818         if (hostHandles == null || hostHandles.length == 0 || handle == null) {
    819             Log.v(this, "isParticipantHost(N) : host or participant uri null");
    820             return false;
    821         }
    822 
    823         // Conference event package participants are identified using SIP URIs (see RFC3261).
    824         // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
    825         // Per RFC3261, the "user" can be a telephone number.
    826         // For example: sip:1650555121;phone-context=blah.com (at) host.com
    827         // In this case, the phone number is in the user field of the URI, and the parameters can be
    828         // ignored.
    829         //
    830         // A SIP URI can also specify a phone number in a format similar to:
    831         // sip:+1-212-555-1212 (at) something.com;user=phone
    832         // In this case, the phone number is again in user field and the parameters can be ignored.
    833         // We can get the user field in these instances by splitting the string on the @, ;, or :
    834         // and looking at the first found item.
    835 
    836         String number = handle.getSchemeSpecificPart();
    837         String numberParts[] = number.split("[@;:]");
    838 
    839         if (numberParts.length == 0) {
    840             Log.v(this, "isParticipantHost(N) : no number in participant handle");
    841             return false;
    842         }
    843         number = numberParts[0];
    844 
    845         for (Uri hostHandle : hostHandles) {
    846             if (hostHandle == null) {
    847                 continue;
    848             }
    849             // The host number will be a tel: uri.  Per RFC3966, the part after tel: is the phone
    850             // number.
    851             String hostNumber = hostHandle.getSchemeSpecificPart();
    852 
    853             // Use a loose comparison of the phone numbers.  This ensures that numbers that differ
    854             // by special characters are counted as equal.
    855             // E.g. +16505551212 would be the same as 16505551212
    856             boolean isHost = PhoneNumberUtils.compare(hostNumber, number);
    857 
    858             Log.v(this, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"),
    859                     Log.pii(hostNumber), Log.pii(number));
    860 
    861             if (isHost) {
    862                 return true;
    863             }
    864         }
    865         return false;
    866     }
    867 
    868     /**
    869      * Handles a change in the original connection backing the conference host connection.  This can
    870      * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
    871      * GSM or CDMA.
    872      * <p>
    873      * If this happens, we will add the conference host connection to telecom and tear down the
    874      * conference.
    875      */
    876     private void handleOriginalConnectionChange() {
    877         if (mConferenceHost == null) {
    878             Log.w(this, "handleOriginalConnectionChange; conference host missing.");
    879             return;
    880         }
    881 
    882         com.android.internal.telephony.Connection originalConnection =
    883                 mConferenceHost.getOriginalConnection();
    884 
    885         if (originalConnection != null &&
    886                 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
    887             Log.i(this,
    888                     "handleOriginalConnectionChange : handover from IMS connection to " +
    889                             "new connection: %s", originalConnection);
    890 
    891             PhoneAccountHandle phoneAccountHandle = null;
    892             if (mConferenceHost.getPhone() != null) {
    893                 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
    894                     Phone imsPhone = mConferenceHost.getPhone();
    895                     // The phone account handle for an ImsPhone is based on the default phone (ie
    896                     // the base GSM or CDMA phone, not on the ImsPhone itself).
    897                     phoneAccountHandle =
    898                             PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
    899                 } else {
    900                     // In the case of SRVCC, we still need a phone account, so use the top level
    901                     // phone to create a phone account.
    902                     phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
    903                             mConferenceHost.getPhone());
    904                 }
    905             }
    906 
    907             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
    908                 Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM");
    909                 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId(),
    910                         mConferenceHost.isOutgoingCall());
    911                 // This is a newly created conference connection as a result of SRVCC
    912                 c.setConferenceSupported(true);
    913                 c.setConnectionProperties(
    914                         c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
    915                 c.updateState();
    916                 // Copy the connect time from the conferenceHost
    917                 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
    918                 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
    919                 mTelephonyConnectionService.addConnectionToConferenceController(c);
    920             } // CDMA case not applicable for SRVCC
    921             mConferenceHost.removeConnectionListener(mConferenceHostListener);
    922             mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
    923             mConferenceHost = null;
    924             setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
    925             disconnectConferenceParticipants();
    926             destroy();
    927         }
    928 
    929         updateStatusHints();
    930     }
    931 
    932     /**
    933      * Changes the state of the Ims conference.
    934      *
    935      * @param state the new state.
    936      */
    937     public void setState(int state) {
    938         Log.v(this, "setState %s", Connection.stateToString(state));
    939 
    940         switch (state) {
    941             case Connection.STATE_INITIALIZING:
    942             case Connection.STATE_NEW:
    943             case Connection.STATE_RINGING:
    944                 // No-op -- not applicable.
    945                 break;
    946             case Connection.STATE_DIALING:
    947                 setDialing();
    948                 break;
    949             case Connection.STATE_DISCONNECTED:
    950                 DisconnectCause disconnectCause;
    951                 if (mConferenceHost == null) {
    952                     disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
    953                 } else {
    954                     disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
    955                             mConferenceHost.getOriginalConnection().getDisconnectCause());
    956                 }
    957                 setDisconnected(disconnectCause);
    958                 disconnectConferenceParticipants();
    959                 destroy();
    960                 break;
    961             case Connection.STATE_ACTIVE:
    962                 setActive();
    963                 break;
    964             case Connection.STATE_HOLDING:
    965                 setOnHold();
    966                 break;
    967         }
    968     }
    969 
    970     /**
    971      * Determines if the host of this conference is capable of video calling.
    972      * @return {@code true} if video capable, {@code false} otherwise.
    973      */
    974     private boolean isVideoCapable() {
    975         int capabilities = mConferenceHost.getConnectionCapabilities();
    976         return can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
    977                 && can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
    978     }
    979 
    980     private void updateStatusHints() {
    981         if (mConferenceHost == null) {
    982             setStatusHints(null);
    983             return;
    984         }
    985 
    986         if (mConferenceHost.isWifi()) {
    987             Phone phone = mConferenceHost.getPhone();
    988             if (phone != null) {
    989                 Context context = phone.getContext();
    990                 setStatusHints(new StatusHints(
    991                         context.getString(R.string.status_hint_label_wifi_call),
    992                         Icon.createWithResource(
    993                                 context.getResources(),
    994                                 R.drawable.ic_signal_wifi_4_bar_24dp),
    995                         null /* extras */));
    996             }
    997         } else {
    998             setStatusHints(null);
    999         }
   1000     }
   1001 
   1002     /**
   1003      * Builds a string representation of the {@link ImsConference}.
   1004      *
   1005      * @return String representing the conference.
   1006      */
   1007     public String toString() {
   1008         StringBuilder sb = new StringBuilder();
   1009         sb.append("[ImsConference objId:");
   1010         sb.append(System.identityHashCode(this));
   1011         sb.append(" telecomCallID:");
   1012         sb.append(getTelecomCallId());
   1013         sb.append(" state:");
   1014         sb.append(Connection.stateToString(getState()));
   1015         sb.append(" hostConnection:");
   1016         sb.append(mConferenceHost);
   1017         sb.append(" participants:");
   1018         sb.append(mConferenceParticipantConnections.size());
   1019         sb.append("]");
   1020         return sb.toString();
   1021     }
   1022 
   1023     private boolean canHoldImsCalls() {
   1024         PersistableBundle b = getCarrierConfig();
   1025         // Return true if the CarrierConfig is unavailable
   1026         return b == null || b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL);
   1027     }
   1028 
   1029     private PersistableBundle getCarrierConfig() {
   1030         if (mConferenceHost == null) {
   1031             return null;
   1032         }
   1033 
   1034         Phone phone = mConferenceHost.getPhone();
   1035         if (phone == null) {
   1036             return null;
   1037         }
   1038         return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId());
   1039     }
   1040 
   1041     /**
   1042      * @return {@code true} if the carrier associated with the conference requires that the maximum
   1043      *      size of the conference is enforced, {@code false} otherwise.
   1044      */
   1045     public boolean isMaximumConferenceSizeEnforced() {
   1046         PersistableBundle b = getCarrierConfig();
   1047         // Return false if the CarrierConfig is unavailable
   1048         return b != null && b.getBoolean(
   1049                 CarrierConfigManager.KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL);
   1050     }
   1051 
   1052     /**
   1053      * @return The maximum size of a conference call where
   1054      * {@link #isMaximumConferenceSizeEnforced()} is true.
   1055      */
   1056     public int getMaximumConferenceSize() {
   1057         PersistableBundle b = getCarrierConfig();
   1058 
   1059         // If there is no carrier config its really a problem, but we'll still define a sane limit
   1060         // of 5 so that we can still make a conference.
   1061         if (b == null) {
   1062             Log.w(this, "getMaximumConferenceSize - failed to get conference size");
   1063             return 5;
   1064         }
   1065         return b.getInt(CarrierConfigManager.KEY_IMS_CONFERENCE_SIZE_LIMIT_INT);
   1066     }
   1067 
   1068     /**
   1069      * @return The number of participants in the conference.
   1070      */
   1071     public int getNumberOfParticipants() {
   1072         return mConferenceParticipantConnections.size();
   1073     }
   1074 
   1075     /**
   1076      * @return {@code True} if the carrier enforces a maximum conference size, and the number of
   1077      *      participants in the conference has reached the limit, {@code false} otherwise.
   1078      */
   1079     public boolean isFullConference() {
   1080         return isMaximumConferenceSizeEnforced()
   1081                 && getNumberOfParticipants() >= getMaximumConferenceSize();
   1082     }
   1083 }
   1084