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