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 newParticipantsAdded = false;
972             boolean oldParticipantsRemoved = false;
973             ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
974             HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size());
975 
976             // Determine if the conference event package represents a single party conference.
977             // A single party conference is one where there is no other participant other than the
978             // conference host and one other participant.
979             // We purposely exclude participants which have a disconnected state in the conference
980             // event package; some carriers are known to keep a disconnected participant around in
981             // subsequent CEP updates with a state of disconnected, even though its no longer part
982             // of the conference.
983             // Note: We consider 0 to still be a single party conference since some carriers will
984             // send a conference event package with JUST the host in it when the conference is
985             // disconnected.  We don't want to change back to conference mode prior to disconnection
986             // or we will not log the call.
987             boolean isSinglePartyConference = participants.stream()
988                     .filter(p -> {
989                         Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint());
990                         return !Objects.equals(mHostParticipantIdentity, pIdent)
991                                 && p.getState() != Connection.STATE_DISCONNECTED;
992                     })
993                     .count() <= 1;
994 
995             // We will only process the CEP data if:
996             // 1. We're not emulating a single party call.
997             // 2. We're emulating a single party call and the CEP contains more than just the
998             //    single party
999             if ((!isMultiparty() && !isSinglePartyConference)
1000                     || isMultiparty()) {
1001                 // Add any new participants and update existing.
1002                 for (ConferenceParticipant participant : participants) {
1003                     Pair<Uri, Uri> userEntity = new Pair<>(participant.getHandle(),
1004                             participant.getEndpoint());
1005 
1006                     // We will exclude disconnected participants from the hash set of tracked
1007                     // participants.  Some carriers are known to leave disconnected participants in
1008                     // the conference event package data which would cause them to be present in the
1009                     // conference even though they're disconnected.  Removing them from the hash set
1010                     // here means we'll clean them up below.
1011                     if (participant.getState() != Connection.STATE_DISCONNECTED) {
1012                         participantUserEntities.add(userEntity);
1013                     }
1014                     if (!mConferenceParticipantConnections.containsKey(userEntity)) {
1015                         // Some carriers will also include the conference host in the CEP.  We will
1016                         // filter that out here.
1017                         if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) {
1018                             createConferenceParticipantConnection(parent, participant);
1019                             newParticipants.add(participant);
1020                             newParticipantsAdded = true;
1021                         } else {
1022                             // Track the identity of the conference host; its useful to know when
1023                             // we look at the CEP in the future.
1024                             mHostParticipantIdentity = userEntity;
1025                         }
1026                     } else {
1027                         ConferenceParticipantConnection connection =
1028                                 mConferenceParticipantConnections.get(userEntity);
1029                         Log.i(this,
1030                                 "handleConferenceParticipantsUpdate: updateState, participant = %s",
1031                                 participant);
1032                         connection.updateState(participant.getState());
1033                         if (participant.getState() == Connection.STATE_DISCONNECTED) {
1034                             /**
1035                              * Per {@link ConferenceParticipantConnection#updateState(int)}, we will
1036                              * destroy the connection when its disconnected.
1037                              */
1038                             handleConnectionDestruction(connection);
1039                         }
1040                         connection.setVideoState(parent.getVideoState());
1041                     }
1042                 }
1043 
1044                 // Set state of new participants.
1045                 if (newParticipantsAdded) {
1046                     // Set the state of the new participants at once and add to the conference
1047                     for (ConferenceParticipant newParticipant : newParticipants) {
1048                         ConferenceParticipantConnection connection =
1049                                 mConferenceParticipantConnections.get(new Pair<>(
1050                                         newParticipant.getHandle(),
1051                                         newParticipant.getEndpoint()));
1052                         connection.updateState(newParticipant.getState());
1053                         /**
1054                          * Per {@link ConferenceParticipantConnection#updateState(int)}, we will
1055                          * destroy the connection when its disconnected.
1056                          */
1057                         if (newParticipant.getState() == Connection.STATE_DISCONNECTED) {
1058                             handleConnectionDestruction(connection);
1059                         }
1060                         connection.setVideoState(parent.getVideoState());
1061                     }
1062                 }
1063 
1064                 // Finally, remove any participants from the conference that no longer exist in the
1065                 // conference event package data.
1066                 Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator =
1067                         mConferenceParticipantConnections.entrySet().iterator();
1068                 while (entryIterator.hasNext()) {
1069                     Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection> entry =
1070                             entryIterator.next();
1071 
1072                     if (!participantUserEntities.contains(entry.getKey())) {
1073                         ConferenceParticipantConnection participant = entry.getValue();
1074                         participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
1075                         removeTelephonyConnection(participant);
1076                         participant.destroy();
1077                         entryIterator.remove();
1078                         oldParticipantsRemoved = true;
1079                     }
1080                 }
1081             }
1082 
1083             int newParticipantCount = mConferenceParticipantConnections.size();
1084             Log.v(this, "handleConferenceParticipantsUpdate: oldParticipantCount=%d, "
1085                             + "newParticipantcount=%d", oldParticipantCount, newParticipantCount);
1086             // If the single party call emulation fature flag is enabled, we can potentially treat
1087             // the conference as a single party call when there is just one participant.
1088             if (mFeatureFlagProxy.isUsingSinglePartyCallEmulation() &&
1089                     !mConferenceHost.isAdhocConferenceCall()) {
1090                 if (oldParticipantCount != 1 && newParticipantCount == 1) {
1091                     // If number of participants goes to 1, emulate a single party call.
1092                     startEmulatingSinglePartyCall();
1093                 } else if (!isMultiparty() && !isSinglePartyConference) {
1094                     // Number of participants increased, so stop emulating a single party call.
1095                     stopEmulatingSinglePartyCall();
1096                 }
1097             }
1098 
1099             // If new participants were added or old ones were removed, we need to ensure the state
1100             // of the manage conference capability is updated.
1101             if (newParticipantsAdded || oldParticipantsRemoved) {
1102                 updateManageConference();
1103             }
1104 
1105             // If the conference is empty and we're supposed to do a local disconnect, do so now.
1106             if (mCarrierConfig.shouldLocalDisconnectEmptyConference()
1107                     // If we dropped from > 0 participants to zero
1108                     // OR if the conference had a single participant and is emulating a standalone
1109                     // call.
1110                     && (oldParticipantCount > 0 || !isMultiparty())
1111                     // AND the CEP says there is nobody left any more.
1112                     && newParticipantCount == 0) {
1113                 Log.i(this, "handleConferenceParticipantsUpdate: empty conference; "
1114                         + "local disconnect.");
1115                 onDisconnect();
1116             }
1117         }
1118     }
1119 
1120     /**
1121      * Called after {@link #startEmulatingSinglePartyCall()} to cause the conference to appear as
1122      * if it is a conference again.
1123      * 1. Tell telecom we're a conference again.
1124      * 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
1125      * 3. Null out the name/address.
1126      *
1127      * Note: Single party call emulation is disabled if the conference is taking place via a
1128      * sim call manager.  Emulating a single party call requires properties of the conference to be
1129      * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
1130      * correctly by the sim call manager to Telecom.
1131      */
stopEmulatingSinglePartyCall()1132     private void stopEmulatingSinglePartyCall() {
1133         if (mIsUsingSimCallManager) {
1134             Log.i(this, "stopEmulatingSinglePartyCall: using sim call manager; skip.");
1135             return;
1136         }
1137 
1138         Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one"
1139                 + " participant; make it look conference-like again.");
1140 
1141         if (mCouldManageConference) {
1142             int currentCapabilities = getConnectionCapabilities();
1143             currentCapabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
1144             setConnectionCapabilities(currentCapabilities);
1145         }
1146 
1147         // Null out the address/name so it doesn't look like a single party call
1148         setAddress(null, TelecomManager.PRESENTATION_UNKNOWN);
1149         setCallerDisplayName(null, TelecomManager.PRESENTATION_UNKNOWN);
1150 
1151         // Copy the conference connect time back to the previous lone participant.
1152         ConferenceParticipantConnection loneParticipant =
1153                 mConferenceParticipantConnections.get(mLoneParticipantIdentity);
1154         if (loneParticipant != null) {
1155             Log.d(this,
1156                     "stopEmulatingSinglePartyCall: restored lone participant connect time");
1157             loneParticipant.setConnectTimeMillis(getConnectionTime());
1158             loneParticipant.setConnectionStartElapsedRealtimeMillis(
1159                     getConnectionStartElapsedRealtimeMillis());
1160         }
1161 
1162         // Tell Telecom its a conference again.
1163         setConferenceState(true);
1164     }
1165 
1166     /**
1167      * Called when a conference drops to a single participant. Causes this conference to present
1168      * itself to Telecom as if it was a single party call.
1169      * 1. Remove the participant from Telecom and from local tracking; when we get a new CEP in
1170      *    the future we'll just re-add the participant anyways.
1171      * 2. Tell telecom we're not a conference.
1172      * 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
1173      * 4. Set the name/address to that of the single participant.
1174      *
1175      * Note: Single party call emulation is disabled if the conference is taking place via a
1176      * sim call manager.  Emulating a single party call requires properties of the conference to be
1177      * changed (connect time, address, conference state) which cannot be guaranteed to be relayed
1178      * correctly by the sim call manager to Telecom.
1179      */
startEmulatingSinglePartyCall()1180     private void startEmulatingSinglePartyCall() {
1181         if (mIsUsingSimCallManager) {
1182             Log.i(this, "startEmulatingSinglePartyCall: using sim call manager; skip.");
1183             return;
1184         }
1185 
1186         Log.i(this, "startEmulatingSinglePartyCall: conference has a single "
1187                 + "participant; downgrade to single party call.");
1188 
1189         Iterator<ConferenceParticipantConnection> valueIterator =
1190                 mConferenceParticipantConnections.values().iterator();
1191         if (valueIterator.hasNext()) {
1192             ConferenceParticipantConnection entry = valueIterator.next();
1193 
1194             // Set the conference name/number to that of the remaining participant.
1195             setAddress(entry.getAddress(), entry.getAddressPresentation());
1196             setCallerDisplayName(entry.getCallerDisplayName(),
1197                     entry.getCallerDisplayNamePresentation());
1198             setConnectionStartElapsedRealtimeMillis(
1199                     entry.getConnectionStartElapsedRealtimeMillis());
1200             setConnectionTime(entry.getConnectTimeMillis());
1201             setCallDirection(entry.getCallDirection());
1202             mLoneParticipantIdentity = new Pair<>(entry.getUserEntity(), entry.getEndpoint());
1203 
1204             // Remove the participant from Telecom.  It'll get picked up in a future CEP update
1205             // again anyways.
1206             entry.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED,
1207                     DisconnectCause.REASON_EMULATING_SINGLE_CALL));
1208             removeTelephonyConnection(entry);
1209             entry.destroy();
1210             valueIterator.remove();
1211         }
1212 
1213         // Have Telecom pretend its not a conference.
1214         setConferenceState(false);
1215 
1216         // Remove manage conference capability.
1217         mCouldManageConference =
1218                 (getConnectionCapabilities() & Connection.CAPABILITY_MANAGE_CONFERENCE) != 0;
1219         int currentCapabilities = getConnectionCapabilities();
1220         currentCapabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
1221         setConnectionCapabilities(currentCapabilities);
1222     }
1223 
1224     /**
1225      * Creates a new {@link ConferenceParticipantConnection} to represent a
1226      * {@link ConferenceParticipant}.
1227      * <p>
1228      * The new connection is added to the conference controller and connection service.
1229      *
1230      * @param parent The connection which was notified of the participant change (e.g. the
1231      *                         parent connection).
1232      * @param participant The conference participant information.
1233      */
createConferenceParticipantConnection( TelephonyConnection parent, ConferenceParticipant participant)1234     private void createConferenceParticipantConnection(
1235             TelephonyConnection parent, ConferenceParticipant participant) {
1236 
1237         // Create and add the new connection in holding state so that it does not become the
1238         // active call.
1239         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
1240                 parent.getOriginalConnection(), participant,
1241                 !isConferenceHost() /* isRemotelyHosted */);
1242 
1243         if (participant.getConnectTime() == 0) {
1244             connection.setConnectTimeMillis(parent.getConnectTimeMillis());
1245             connection.setConnectionStartElapsedRealtimeMillis(
1246                     parent.getConnectionStartElapsedRealtimeMillis());
1247         } else {
1248             connection.setConnectTimeMillis(participant.getConnectTime());
1249             connection.setConnectionStartElapsedRealtimeMillis(participant.getConnectElapsedTime());
1250         }
1251         // Indicate whether this is an MT or MO call to Telecom; the participant has the cached
1252         // data from the time of merge.
1253         connection.setCallDirection(participant.getCallDirection());
1254 
1255         // Ensure important attributes of the parent get copied to child.
1256         connection.setConnectionProperties(applyHostPropertiesToChild(
1257                 connection.getConnectionProperties(), parent.getConnectionProperties()));
1258         connection.setStatusHints(parent.getStatusHints());
1259         connection.setExtras(getChildExtrasFromHostBundle(parent.getExtras()));
1260 
1261         Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s",
1262                 participant, connection);
1263 
1264         synchronized(mUpdateSyncRoot) {
1265             mConferenceParticipantConnections.put(new Pair<>(participant.getHandle(),
1266                     participant.getEndpoint()), connection);
1267         }
1268 
1269         mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
1270                 connection, this);
1271         addTelephonyConnection(connection);
1272     }
1273 
1274     /**
1275      * Removes a conference participant from the conference.
1276      *
1277      * @param participant The participant to remove.
1278      */
removeConferenceParticipant(ConferenceParticipantConnection participant)1279     private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
1280         Log.i(this, "removeConferenceParticipant: %s", participant);
1281 
1282         synchronized(mUpdateSyncRoot) {
1283             mConferenceParticipantConnections.remove(new Pair<>(participant.getUserEntity(),
1284                     participant.getEndpoint()));
1285         }
1286         participant.destroy();
1287     }
1288 
1289     /**
1290      * Disconnects all conference participants from the conference.
1291      */
disconnectConferenceParticipants()1292     private void disconnectConferenceParticipants() {
1293         Log.v(this, "disconnectConferenceParticipants");
1294 
1295         synchronized(mUpdateSyncRoot) {
1296             for (ConferenceParticipantConnection connection :
1297                     mConferenceParticipantConnections.values()) {
1298 
1299                 // Mark disconnect cause as cancelled to ensure that the call is not logged in the
1300                 // call log.
1301                 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
1302                 connection.destroy();
1303             }
1304             mConferenceParticipantConnections.clear();
1305             updateManageConference();
1306         }
1307     }
1308 
1309     /**
1310      * Extracts a phone number from a {@link Uri}.
1311      * <p>
1312      * Phone numbers can be represented either as a TEL URI or a SIP URI.
1313      * For conference event packages, RFC3261 specifies how participants can be identified using a
1314      * SIP URI.
1315      * A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
1316      * Per RFC3261, the "user" can be a telephone number.
1317      * For example: sip:1650555121;phone-context=blah.com@host.com
1318      * In this case, the phone number is in the user field of the URI, and the parameters can be
1319      * ignored.
1320      *
1321      * A SIP URI can also specify a phone number in a format similar to:
1322      * sip:+1-212-555-1212@something.com;user=phone
1323      * In this case, the phone number is again in user field and the parameters can be ignored.
1324      * We can get the user field in these instances by splitting the string on the @, ;, or :
1325      * and looking at the first found item.
1326      * @param handle The URI containing a SIP or TEL formatted phone number.
1327      * @return extracted phone number.
1328      */
extractPhoneNumber(@onNull Uri handle)1329     private static @NonNull String extractPhoneNumber(@NonNull Uri handle) {
1330         // Number is always in the scheme specific part, regardless of whether this is a TEL or SIP
1331         // URI.
1332         String number = handle.getSchemeSpecificPart();
1333         // Get anything before the @ for the SIP case.
1334         String[] numberParts = number.split("[@;:]");
1335 
1336         if (numberParts.length == 0) {
1337             Log.v(LOG_TAG, "extractPhoneNumber(N) : no number in handle");
1338             return "";
1339         }
1340         return numberParts[0];
1341     }
1342 
1343     /**
1344      * Determines if the passed in participant handle is the same as the conference host's handle.
1345      * Starts with a simple equality check.  However, the handles from a conference event package
1346      * will be a SIP uri, so we need to pull that apart to look for the participant's phone number.
1347      *
1348      * @param hostHandles The handle(s) of the connection hosting the conference, typically obtained
1349      *                    from P-Associated-Uri entries.
1350      * @param handle The handle of the conference participant.
1351      * @return {@code true} if the host's handle matches the participant's handle, {@code false}
1352      *      otherwise.
1353      */
1354     @VisibleForTesting
isParticipantHost(Uri[] hostHandles, Uri handle)1355     public static boolean isParticipantHost(Uri[] hostHandles, Uri handle) {
1356         // If there is no host handle or no participant handle, bail early.
1357         if (hostHandles == null || hostHandles.length == 0 || handle == null) {
1358             Log.v(LOG_TAG, "isParticipantHost(N) : host or participant uri null");
1359             return false;
1360         }
1361 
1362         String number = extractPhoneNumber(handle);
1363         // If we couldn't extract the participant's number, then we can't determine if it is the
1364         // host or not.
1365         if (TextUtils.isEmpty(number)) {
1366             return false;
1367         }
1368 
1369         for (Uri hostHandle : hostHandles) {
1370             if (hostHandle == null) {
1371                 continue;
1372             }
1373             // Similar to the CEP participant data, the host identity in the P-Associated-Uri could
1374             // be a SIP URI or a TEL URI.
1375             String hostNumber = extractPhoneNumber(hostHandle);
1376 
1377             // Use a loose comparison of the phone numbers.  This ensures that numbers that differ
1378             // by special characters are counted as equal.
1379             // E.g. +16505551212 would be the same as 16505551212
1380             boolean isHost = PhoneNumberUtils.compare(hostNumber, number);
1381 
1382             Log.v(LOG_TAG, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"),
1383                     Rlog.pii(LOG_TAG, hostNumber), Rlog.pii(LOG_TAG, number));
1384 
1385             if (isHost) {
1386                 return true;
1387             }
1388         }
1389         return false;
1390     }
1391 
1392     /**
1393      * Handles a change in the original connection backing the conference host connection.  This can
1394      * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
1395      * GSM or CDMA.
1396      * <p>
1397      * If this happens, we will add the conference host connection to telecom and tear down the
1398      * conference.
1399      */
handleOriginalConnectionChange()1400     private void handleOriginalConnectionChange() {
1401         if (mConferenceHost == null) {
1402             Log.w(this, "handleOriginalConnectionChange; conference host missing.");
1403             return;
1404         }
1405 
1406         com.android.internal.telephony.Connection originalConnection =
1407                 mConferenceHost.getOriginalConnection();
1408 
1409         if (originalConnection != null &&
1410                 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
1411             Log.i(this,
1412                     "handleOriginalConnectionChange : handover from IMS connection to " +
1413                             "new connection: %s", originalConnection);
1414 
1415             PhoneAccountHandle phoneAccountHandle = null;
1416             if (mConferenceHost.getPhone() != null) {
1417                 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
1418                     Phone imsPhone = mConferenceHost.getPhone();
1419                     // The phone account handle for an ImsPhone is based on the default phone (ie
1420                     // the base GSM or CDMA phone, not on the ImsPhone itself).
1421                     phoneAccountHandle =
1422                             PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
1423                 } else {
1424                     // In the case of SRVCC, we still need a phone account, so use the top level
1425                     // phone to create a phone account.
1426                     phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
1427                             mConferenceHost.getPhone());
1428                 }
1429             }
1430 
1431             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
1432                 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId(),
1433                         mConferenceHost.getCallDirection());
1434                 Log.i(this, "handleOriginalConnectionChange : SRVCC to GSM."
1435                         + " Created new GsmConnection with objId=" + System.identityHashCode(c)
1436                         + " and originalConnection objId="
1437                         + System.identityHashCode(originalConnection));
1438                 // This is a newly created conference connection as a result of SRVCC
1439                 c.setConferenceSupported(true);
1440                 c.setTelephonyConnectionProperties(
1441                         c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
1442                 c.updateState();
1443                 // Copy the connect time from the conferenceHost
1444                 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
1445                 c.setConnectionStartElapsedRealtimeMillis(
1446                         mConferenceHost.getConnectionStartElapsedRealtimeMillis());
1447                 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
1448                 mTelephonyConnectionService.addConnectionToConferenceController(c);
1449             } // CDMA case not applicable for SRVCC
1450             mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
1451             mConferenceHost = null;
1452             setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
1453             disconnectConferenceParticipants();
1454             destroyTelephonyConference();
1455         }
1456 
1457         updateStatusHints();
1458     }
1459 
1460     /**
1461      * Changes the state of the Ims conference.
1462      *
1463      * @param state the new state.
1464      */
setState(int state)1465     public void setState(int state) {
1466         Log.v(this, "setState %s", Connection.stateToString(state));
1467 
1468         switch (state) {
1469             case Connection.STATE_INITIALIZING:
1470             case Connection.STATE_NEW:
1471                 // No-op -- not applicable.
1472                 break;
1473             case Connection.STATE_RINGING:
1474                 setConferenceOnRinging();
1475                 break;
1476             case Connection.STATE_DIALING:
1477                 setConferenceOnDialing();
1478                 break;
1479             case Connection.STATE_DISCONNECTED:
1480                 DisconnectCause disconnectCause;
1481                 if (mConferenceHost == null) {
1482                     disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
1483                 } else {
1484                     if (mConferenceHost.getPhone() != null) {
1485                         disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
1486                                 mConferenceHost.getOriginalConnection().getDisconnectCause(),
1487                                 null, mConferenceHost.getPhone().getPhoneId());
1488                     } else {
1489                         disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
1490                                 mConferenceHost.getOriginalConnection().getDisconnectCause());
1491                     }
1492                 }
1493                 setDisconnected(disconnectCause);
1494                 disconnectConferenceParticipants();
1495                 destroyTelephonyConference();
1496                 break;
1497             case Connection.STATE_ACTIVE:
1498                 setConferenceOnActive();
1499                 break;
1500             case Connection.STATE_HOLDING:
1501                 setConferenceOnHold();
1502                 break;
1503         }
1504     }
1505 
1506     /**
1507      * Determines if the host of this conference is capable of video calling.
1508      * @return {@code true} if video capable, {@code false} otherwise.
1509      */
isVideoCapable()1510     private boolean isVideoCapable() {
1511         int capabilities = mConferenceHost.getConnectionCapabilities();
1512         return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0
1513                 && (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0;
1514     }
1515 
updateStatusHints()1516     private void updateStatusHints() {
1517         if (mConferenceHost == null) {
1518             setStatusHints(null);
1519             return;
1520         }
1521 
1522         if (mConferenceHost.isWifi()) {
1523             Phone phone = mConferenceHost.getPhone();
1524             if (phone != null) {
1525                 Context context = phone.getContext();
1526                 StatusHints hints = new StatusHints(
1527                         context.getString(R.string.status_hint_label_wifi_call),
1528                         Icon.createWithResource(
1529                                 context, R.drawable.ic_signal_wifi_4_bar_24dp),
1530                         null /* extras */);
1531                 setStatusHints(hints);
1532 
1533                 // Ensure the children know they're a WIFI call as well.
1534                 for (Connection c : getConnections()) {
1535                     c.setStatusHints(hints);
1536                 }
1537             }
1538         } else {
1539             setStatusHints(null);
1540         }
1541     }
1542 
1543     /**
1544      * Updates the conference's properties based on changes to the host.
1545      * Also ensures pertinent properties from the host such as the WIFI property are copied to the
1546      * children as well.
1547      * @param connectionProperties The new host properties.
1548      */
updateConnectionProperties(int connectionProperties)1549     private void updateConnectionProperties(int connectionProperties) {
1550         int properties = ImsConference.this.getConnectionProperties();
1551         setConnectionProperties(applyHostProperties(properties, connectionProperties));
1552 
1553         for (Connection c : getConnections()) {
1554             c.setConnectionProperties(applyHostPropertiesToChild(c.getConnectionProperties(),
1555                     connectionProperties));
1556         }
1557     }
1558 
1559     /**
1560      * Updates extras in the conference based on changes made in the parent.
1561      * Also copies select extras (e.g. EXTRA_CALL_NETWORK_TYPE) to the children as well.
1562      * @param extras The extras to copy.
1563      */
updateExtras(Bundle extras)1564     private void updateExtras(Bundle extras) {
1565         putExtras(extras);
1566 
1567         if (extras == null) {
1568             return;
1569         }
1570 
1571         Bundle childBundle = getChildExtrasFromHostBundle(extras);
1572         for (Connection c : getConnections()) {
1573             c.putExtras(childBundle);
1574         }
1575     }
1576 
1577     /**
1578      * Given an extras bundle from the host, returns a new bundle containing those extras which are
1579      * releveant to the children.
1580      * @param extras The host extras.
1581      * @return The extras pertinent to the children.
1582      */
getChildExtrasFromHostBundle(Bundle extras)1583     private Bundle getChildExtrasFromHostBundle(Bundle extras) {
1584         Bundle extrasToCopy = new Bundle();
1585         if (extras != null && extras.containsKey(TelecomManager.EXTRA_CALL_NETWORK_TYPE)) {
1586             int networkType = extras.getInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE);
1587             extrasToCopy.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE, networkType);
1588         }
1589         return extrasToCopy;
1590     }
1591 
1592     /**
1593      * Given the properties from a conference host applies and changes to the host's properties to
1594      * the child as well.
1595      * @param childProperties The existing child properties.
1596      * @param hostProperties The properties from the host.
1597      * @return The child properties with the applicable host bits set/unset.
1598      */
applyHostPropertiesToChild(int childProperties, int hostProperties)1599     private int applyHostPropertiesToChild(int childProperties, int hostProperties) {
1600         childProperties = changeBitmask(childProperties,
1601                 Connection.PROPERTY_WIFI,
1602                 (hostProperties & Connection.PROPERTY_WIFI) != 0);
1603         return childProperties;
1604     }
1605 
1606     /**
1607      * Builds a string representation of the {@link ImsConference}.
1608      *
1609      * @return String representing the conference.
1610      */
toString()1611     public String toString() {
1612         StringBuilder sb = new StringBuilder();
1613         sb.append("[ImsConference objId:");
1614         sb.append(System.identityHashCode(this));
1615         sb.append(" telecomCallID:");
1616         sb.append(getTelecomCallId());
1617         sb.append(" state:");
1618         sb.append(Connection.stateToString(getState()));
1619         sb.append(" hostConnection:");
1620         sb.append(mConferenceHost);
1621         sb.append(" participants:");
1622         sb.append(mConferenceParticipantConnections.size());
1623         sb.append("]");
1624         return sb.toString();
1625     }
1626 
1627     /**
1628      * @return The number of participants in the conference.
1629      */
getNumberOfParticipants()1630     public int getNumberOfParticipants() {
1631         return mConferenceParticipantConnections.size();
1632     }
1633 
1634     /**
1635      * @return {@code True} if the carrier enforces a maximum conference size, and the number of
1636      *      participants in the conference has reached the limit, {@code false} otherwise.
1637      */
isFullConference()1638     public boolean isFullConference() {
1639         return mCarrierConfig.isMaximumConferenceSizeEnforced()
1640                 && getNumberOfParticipants() >= mCarrierConfig.getMaximumConferenceSize();
1641     }
1642 
1643     /**
1644      * Handles destruction of a {@link ConferenceParticipantConnection}.
1645      * We remove the participant from the list of tracked participants in the conference and
1646      * update whether the conference can be managed.
1647      * @param participant the conference participant.
1648      */
handleConnectionDestruction(ConferenceParticipantConnection participant)1649     private void handleConnectionDestruction(ConferenceParticipantConnection participant) {
1650         removeConferenceParticipant(participant);
1651         updateManageConference();
1652     }
1653 }
1654