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