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