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