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