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