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