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 17package com.android.services.telephony; 18 19import android.content.Context; 20import android.graphics.drawable.Icon; 21import android.net.Uri; 22import android.os.Bundle; 23import android.os.PersistableBundle; 24import android.telecom.Conference; 25import android.telecom.ConferenceParticipant; 26import android.telecom.Connection.VideoProvider; 27import android.telecom.Connection; 28import android.telecom.DisconnectCause; 29import android.telecom.Log; 30import android.telecom.PhoneAccountHandle; 31import android.telecom.StatusHints; 32import android.telecom.VideoProfile; 33import android.telephony.CarrierConfigManager; 34import android.telephony.PhoneNumberUtils; 35import android.util.Pair; 36 37import com.android.internal.telephony.Call; 38import com.android.internal.telephony.CallStateException; 39import com.android.internal.telephony.Phone; 40import com.android.internal.telephony.PhoneConstants; 41import com.android.phone.PhoneGlobals; 42import com.android.phone.PhoneUtils; 43import com.android.phone.R; 44 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.HashMap; 48import java.util.HashSet; 49import java.util.Iterator; 50import java.util.List; 51import java.util.Map; 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 */ 68public class ImsConference extends Conference { 69 70 /** 71 * Listener used to respond to changes to conference participants. At the conference level we 72 * are most concerned with handling destruction of a conference participant. 73 */ 74 private final Connection.Listener mParticipantListener = new Connection.Listener() { 75 /** 76 * Participant has been destroyed. Remove it from the conference. 77 * 78 * @param connection The participant which was destroyed. 79 */ 80 @Override 81 public void onDestroyed(Connection connection) { 82 ConferenceParticipantConnection participant = 83 (ConferenceParticipantConnection) connection; 84 removeConferenceParticipant(participant); 85 updateManageConference(); 86 } 87 88 }; 89 90 /** 91 * Listener used to respond to changes to the underlying radio connection for the conference 92 * host connection. Used to respond to SRVCC changes. 93 */ 94 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 95 new TelephonyConnection.TelephonyConnectionListener() { 96 97 @Override 98 public void onOriginalConnectionConfigured(TelephonyConnection c) { 99 if (c == mConferenceHost) { 100 handleOriginalConnectionChange(); 101 } 102 } 103 }; 104 105 /** 106 * Listener used to respond to changes to the connection to the IMS conference server. 107 */ 108 private final android.telecom.Connection.Listener mConferenceHostListener = 109 new android.telecom.Connection.Listener() { 110 111 /** 112 * Updates the state of the conference based on the new state of the host. 113 * 114 * @param c The host connection. 115 * @param state The new state 116 */ 117 @Override 118 public void onStateChanged(android.telecom.Connection c, int state) { 119 setState(state); 120 } 121 122 /** 123 * Disconnects the conference when its host connection disconnects. 124 * 125 * @param c The host connection. 126 * @param disconnectCause The host connection disconnect cause. 127 */ 128 @Override 129 public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) { 130 setDisconnected(disconnectCause); 131 } 132 133 /** 134 * Handles destruction of the host connection; once the host connection has been 135 * destroyed, cleans up the conference participant connection. 136 * 137 * @param connection The host connection. 138 */ 139 @Override 140 public void onDestroyed(android.telecom.Connection connection) { 141 disconnectConferenceParticipants(); 142 } 143 144 /** 145 * Handles changes to conference participant data as reported by the conference host 146 * connection. 147 * 148 * @param c The connection. 149 * @param participants The participant information. 150 */ 151 @Override 152 public void onConferenceParticipantsChanged(android.telecom.Connection c, 153 List<ConferenceParticipant> participants) { 154 155 if (c == null || participants == null) { 156 return; 157 } 158 Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size()); 159 TelephonyConnection telephonyConnection = (TelephonyConnection) c; 160 handleConferenceParticipantsUpdate(telephonyConnection, participants); 161 } 162 163 @Override 164 public void onVideoStateChanged(android.telecom.Connection c, int videoState) { 165 Log.d(this, "onVideoStateChanged video state %d", videoState); 166 setVideoState(c, videoState); 167 } 168 169 @Override 170 public void onVideoProviderChanged(android.telecom.Connection c, 171 Connection.VideoProvider videoProvider) { 172 Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, 173 videoProvider); 174 setVideoProvider(c, videoProvider); 175 } 176 177 @Override 178 public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) { 179 Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s," + 180 " connectionCapabilities: %s", c, connectionCapabilities); 181 int capabilites = ImsConference.this.getConnectionCapabilities(); 182 boolean isVideoConferencingSupported = mConferenceHost == null ? false : 183 mConferenceHost.isCarrierVideoConferencingSupported(); 184 setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities, 185 isVideoConferencingSupported)); 186 } 187 188 @Override 189 public void onConnectionPropertiesChanged(Connection c, int connectionProperties) { 190 Log.d(this, "onConnectionPropertiesChanged: Connection: %s," + 191 " connectionProperties: %s", c, connectionProperties); 192 int properties = ImsConference.this.getConnectionProperties(); 193 setConnectionProperties(applyHostProperties(properties, connectionProperties)); 194 } 195 196 @Override 197 public void onStatusHintsChanged(Connection c, StatusHints statusHints) { 198 Log.v(this, "onStatusHintsChanged"); 199 updateStatusHints(); 200 } 201 202 @Override 203 public void onExtrasChanged(Connection c, Bundle extras) { 204 Log.v(this, "onExtrasChanged: c=" + c + " Extras=" + extras); 205 putExtras(extras); 206 } 207 208 @Override 209 public void onExtrasRemoved(Connection c, List<String> keys) { 210 Log.v(this, "onExtrasRemoved: c=" + c + " key=" + keys); 211 removeExtras(keys); 212 } 213 }; 214 215 /** 216 * The telephony connection service; used to add new participant connections to Telecom. 217 */ 218 private TelephonyConnectionService mTelephonyConnectionService; 219 220 /** 221 * The connection to the conference server which is hosting the conference. 222 */ 223 private TelephonyConnection mConferenceHost; 224 225 /** 226 * The PhoneAccountHandle of the conference host. 227 */ 228 private PhoneAccountHandle mConferenceHostPhoneAccountHandle; 229 230 /** 231 * The address of the conference host. 232 */ 233 private Uri[] mConferenceHostAddress; 234 235 /** 236 * The known conference participant connections. The HashMap is keyed by a Pair containing 237 * the handle and endpoint Uris. 238 * Access to the hashmap is protected by the {@link #mUpdateSyncRoot}. 239 */ 240 private final HashMap<Pair<Uri, Uri>, ConferenceParticipantConnection> 241 mConferenceParticipantConnections = new HashMap<>(); 242 243 /** 244 * Sychronization root used to ensure that updates to the 245 * {@link #mConferenceParticipantConnections} happen atomically are are not interleaved across 246 * threads. There are some instances where the network will send conference event package 247 * data closely spaced. If that happens, it is possible that the interleaving of the update 248 * will cause duplicate participant info to be added. 249 */ 250 private final Object mUpdateSyncRoot = new Object(); 251 252 public void updateConferenceParticipantsAfterCreation() { 253 if (mConferenceHost != null) { 254 Log.v(this, "updateConferenceStateAfterCreation :: process participant update"); 255 handleConferenceParticipantsUpdate(mConferenceHost, 256 mConferenceHost.getConferenceParticipants()); 257 } else { 258 Log.v(this, "updateConferenceStateAfterCreation :: null mConferenceHost"); 259 } 260 } 261 262 /** 263 * Initializes a new {@link ImsConference}. 264 * 265 * @param telephonyConnectionService The connection service responsible for adding new 266 * conferene participants. 267 * @param conferenceHost The telephony connection hosting the conference. 268 * @param phoneAccountHandle The phone account handle associated with the conference. 269 */ 270 public ImsConference(TelephonyConnectionService telephonyConnectionService, 271 TelephonyConnection conferenceHost, PhoneAccountHandle phoneAccountHandle) { 272 273 super(phoneAccountHandle); 274 275 // Specify the connection time of the conference to be the connection time of the original 276 // connection. 277 long connectTime = conferenceHost.getOriginalConnection().getConnectTime(); 278 setConnectTimeMillis(connectTime); 279 // Set the connectTime in the connection as well. 280 conferenceHost.setConnectTimeMillis(connectTime); 281 282 mTelephonyConnectionService = telephonyConnectionService; 283 setConferenceHost(conferenceHost); 284 285 int capabilities = Connection.CAPABILITY_MUTE | 286 Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN; 287 if (canHoldImsCalls()) { 288 capabilities |= Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD; 289 } 290 capabilities = applyHostCapabilities(capabilities, 291 mConferenceHost.getConnectionCapabilities(), 292 mConferenceHost.isCarrierVideoConferencingSupported()); 293 setConnectionCapabilities(capabilities); 294 295 } 296 297 /** 298 * Transfers capabilities from the conference host to the conference itself. 299 * 300 * @param conferenceCapabilities The current conference capabilities. 301 * @param capabilities The new conference host capabilities. 302 * @param isVideoConferencingSupported Whether video conferencing is supported. 303 * @return The merged capabilities to be applied to the conference. 304 */ 305 private int applyHostCapabilities(int conferenceCapabilities, int capabilities, 306 boolean isVideoConferencingSupported) { 307 308 conferenceCapabilities = changeBitmask(conferenceCapabilities, 309 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 310 can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 311 312 if (isVideoConferencingSupported) { 313 conferenceCapabilities = changeBitmask(conferenceCapabilities, 314 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 315 can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 316 conferenceCapabilities = changeBitmask(conferenceCapabilities, 317 Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, 318 can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)); 319 } else { 320 // If video conferencing is not supported, explicitly turn off the remote video 321 // capability and the ability to upgrade to video. 322 Log.v(this, "applyHostCapabilities : video conferencing not supported"); 323 conferenceCapabilities = changeBitmask(conferenceCapabilities, 324 Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, false); 325 conferenceCapabilities = changeBitmask(conferenceCapabilities, 326 Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, false); 327 } 328 329 conferenceCapabilities = changeBitmask(conferenceCapabilities, 330 Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, 331 can(capabilities, Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO)); 332 333 return conferenceCapabilities; 334 } 335 336 /** 337 * Transfers properties from the conference host to the conference itself. 338 * 339 * @param conferenceProperties The current conference properties. 340 * @param properties The new conference host properties. 341 * @return The merged properties to be applied to the conference. 342 */ 343 private int applyHostProperties(int conferenceProperties, int properties) { 344 conferenceProperties = changeBitmask(conferenceProperties, 345 Connection.PROPERTY_HIGH_DEF_AUDIO, 346 can(properties, Connection.PROPERTY_HIGH_DEF_AUDIO)); 347 348 conferenceProperties = changeBitmask(conferenceProperties, 349 Connection.PROPERTY_WIFI, 350 can(properties, Connection.PROPERTY_WIFI)); 351 352 conferenceProperties = changeBitmask(conferenceProperties, 353 Connection.PROPERTY_IS_EXTERNAL_CALL, 354 can(properties, Connection.PROPERTY_IS_EXTERNAL_CALL)); 355 356 return conferenceProperties; 357 } 358 359 /** 360 * Not used by the IMS conference controller. 361 * 362 * @return {@code Null}. 363 */ 364 @Override 365 public android.telecom.Connection getPrimaryConnection() { 366 return null; 367 } 368 369 /** 370 * Returns VideoProvider of the conference. This can be null. 371 * 372 * @hide 373 */ 374 @Override 375 public VideoProvider getVideoProvider() { 376 if (mConferenceHost != null) { 377 return mConferenceHost.getVideoProvider(); 378 } 379 return null; 380 } 381 382 /** 383 * Returns video state of conference 384 * 385 * @hide 386 */ 387 @Override 388 public int getVideoState() { 389 if (mConferenceHost != null) { 390 return mConferenceHost.getVideoState(); 391 } 392 return VideoProfile.STATE_AUDIO_ONLY; 393 } 394 395 /** 396 * Invoked when the Conference and all its {@link Connection}s should be disconnected. 397 * <p> 398 * Hangs up the call via the conference host connection. When the host connection has been 399 * successfully disconnected, the {@link #mConferenceHostListener} listener receives an 400 * {@code onDestroyed} event, which triggers the conference participant connections to be 401 * disconnected. 402 */ 403 @Override 404 public void onDisconnect() { 405 Log.v(this, "onDisconnect: hanging up conference host."); 406 if (mConferenceHost == null) { 407 return; 408 } 409 410 Call call = mConferenceHost.getCall(); 411 if (call != null) { 412 try { 413 call.hangup(); 414 } catch (CallStateException e) { 415 Log.e(this, e, "Exception thrown trying to hangup conference"); 416 } 417 } 418 } 419 420 /** 421 * Invoked when the specified {@link android.telecom.Connection} should be separated from the 422 * conference call. 423 * <p> 424 * IMS does not support separating connections from the conference. 425 * 426 * @param connection The connection to separate. 427 */ 428 @Override 429 public void onSeparate(android.telecom.Connection connection) { 430 Log.wtf(this, "Cannot separate connections from an IMS conference."); 431 } 432 433 /** 434 * Invoked when the specified {@link android.telecom.Connection} should be merged into the 435 * conference call. 436 * 437 * @param connection The {@code Connection} to merge. 438 */ 439 @Override 440 public void onMerge(android.telecom.Connection connection) { 441 try { 442 Phone phone = mConferenceHost.getPhone(); 443 if (phone != null) { 444 phone.conference(); 445 } 446 } catch (CallStateException e) { 447 Log.e(this, e, "Exception thrown trying to merge call into a conference"); 448 } 449 } 450 451 /** 452 * Invoked when the conference should be put on hold. 453 */ 454 @Override 455 public void onHold() { 456 if (mConferenceHost == null) { 457 return; 458 } 459 mConferenceHost.performHold(); 460 } 461 462 /** 463 * Invoked when the conference should be moved from hold to active. 464 */ 465 @Override 466 public void onUnhold() { 467 if (mConferenceHost == null) { 468 return; 469 } 470 mConferenceHost.performUnhold(); 471 } 472 473 /** 474 * Invoked to play a DTMF tone. 475 * 476 * @param c A DTMF character. 477 */ 478 @Override 479 public void onPlayDtmfTone(char c) { 480 if (mConferenceHost == null) { 481 return; 482 } 483 mConferenceHost.onPlayDtmfTone(c); 484 } 485 486 /** 487 * Invoked to stop playing a DTMF tone. 488 */ 489 @Override 490 public void onStopDtmfTone() { 491 if (mConferenceHost == null) { 492 return; 493 } 494 mConferenceHost.onStopDtmfTone(); 495 } 496 497 /** 498 * Handles the addition of connections to the {@link ImsConference}. The 499 * {@link ImsConferenceController} does not add connections to the conference. 500 * 501 * @param connection The newly added connection. 502 */ 503 @Override 504 public void onConnectionAdded(android.telecom.Connection connection) { 505 // No-op 506 } 507 508 /** 509 * Changes a bit-mask to add or remove a bit-field. 510 * 511 * @param bitmask The bit-mask. 512 * @param bitfield The bit-field to change. 513 * @param enabled Whether the bit-field should be set or removed. 514 * @return The bit-mask with the bit-field changed. 515 */ 516 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 517 if (enabled) { 518 return bitmask | bitfield; 519 } else { 520 return bitmask & ~bitfield; 521 } 522 } 523 524 /** 525 * Determines if this conference is hosted on the current device or the peer device. 526 * 527 * @return {@code true} if this conference is hosted on the current device, {@code false} if it 528 * is hosted on the peer device. 529 */ 530 public boolean isConferenceHost() { 531 if (mConferenceHost == null) { 532 return false; 533 } 534 com.android.internal.telephony.Connection originalConnection = 535 mConferenceHost.getOriginalConnection(); 536 537 return originalConnection != null && originalConnection.isMultiparty() && 538 originalConnection.isConferenceHost(); 539 } 540 541 /** 542 * Updates the manage conference capability of the conference. Where there are one or more 543 * conference event package participants, the conference management is permitted. Where there 544 * are no conference event package participants, conference management is not permitted. 545 * <p> 546 * Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure 547 * that the conference is represented appropriately on Bluetooth devices. 548 */ 549 private void updateManageConference() { 550 boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE); 551 boolean canManageConference = !mConferenceParticipantConnections.isEmpty(); 552 Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N", 553 canManageConference ? "Y" : "N"); 554 555 if (couldManageConference != canManageConference) { 556 int capabilities = getConnectionCapabilities(); 557 558 if (canManageConference) { 559 capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE; 560 capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN; 561 } else { 562 capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE; 563 capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN; 564 } 565 566 setConnectionCapabilities(capabilities); 567 } 568 } 569 570 /** 571 * Sets the connection hosting the conference and registers for callbacks. 572 * 573 * @param conferenceHost The connection hosting the conference. 574 */ 575 private void setConferenceHost(TelephonyConnection conferenceHost) { 576 if (Log.VERBOSE) { 577 Log.v(this, "setConferenceHost " + conferenceHost); 578 } 579 580 mConferenceHost = conferenceHost; 581 582 // Attempt to get the conference host's address (e.g. the host's own phone number). 583 // We need to look at the default phone for the ImsPhone when creating the phone account 584 // for the 585 if (mConferenceHost.getPhone() != null && 586 mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 587 // Look up the conference host's address; we need this later for filtering out the 588 // conference host in conference event package data. 589 Phone imsPhone = mConferenceHost.getPhone(); 590 mConferenceHostPhoneAccountHandle = 591 PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone()); 592 Uri hostAddress = TelecomAccountRegistry.getInstance(mTelephonyConnectionService) 593 .getAddress(mConferenceHostPhoneAccountHandle); 594 595 ArrayList<Uri> hostAddresses = new ArrayList<>(); 596 597 // add address from TelecomAccountRegistry 598 if (hostAddress != null) { 599 hostAddresses.add(hostAddress); 600 } 601 602 // add addresses from phone 603 if (imsPhone.getCurrentSubscriberUris() != null) { 604 hostAddresses.addAll( 605 new ArrayList<>(Arrays.asList(imsPhone.getCurrentSubscriberUris()))); 606 } 607 608 mConferenceHostAddress = new Uri[hostAddresses.size()]; 609 mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress); 610 } 611 612 mConferenceHost.addConnectionListener(mConferenceHostListener); 613 mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener); 614 setConnectionCapabilities(applyHostCapabilities(getConnectionCapabilities(), 615 mConferenceHost.getConnectionCapabilities(), 616 mConferenceHost.isCarrierVideoConferencingSupported())); 617 setConnectionProperties(applyHostProperties(getConnectionProperties(), 618 mConferenceHost.getConnectionProperties())); 619 620 setState(mConferenceHost.getState()); 621 updateStatusHints(); 622 } 623 624 /** 625 * Handles state changes for conference participant(s). The participants data passed in 626 * 627 * @param parent The connection which was notified of the conference participant. 628 * @param participants The conference participant information. 629 */ 630 private void handleConferenceParticipantsUpdate( 631 TelephonyConnection parent, List<ConferenceParticipant> participants) { 632 633 if (participants == null) { 634 return; 635 } 636 637 Log.i(this, "handleConferenceParticipantsUpdate: size=%d", participants.size()); 638 639 // Perform the update in a synchronized manner. It is possible for the IMS framework to 640 // trigger two onConferenceParticipantsChanged callbacks in quick succession. If the first 641 // update adds new participants, and the second does something like update the status of one 642 // of the participants, we can get into a situation where the participant is added twice. 643 synchronized (mUpdateSyncRoot) { 644 boolean newParticipantsAdded = false; 645 boolean oldParticipantsRemoved = false; 646 ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size()); 647 HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size()); 648 649 // Add any new participants and update existing. 650 for (ConferenceParticipant participant : participants) { 651 Pair<Uri,Uri> userEntity = new Pair<>(participant.getHandle(), 652 participant.getEndpoint()); 653 654 participantUserEntities.add(userEntity); 655 if (!mConferenceParticipantConnections.containsKey(userEntity)) { 656 // Some carriers will also include the conference host in the CEP. We will 657 // filter that out here. 658 if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) { 659 createConferenceParticipantConnection(parent, participant); 660 newParticipants.add(participant); 661 newParticipantsAdded = true; 662 } 663 } else { 664 ConferenceParticipantConnection connection = 665 mConferenceParticipantConnections.get(userEntity); 666 Log.i(this, "handleConferenceParticipantsUpdate: updateState, participant = %s", 667 participant); 668 connection.updateState(participant.getState()); 669 } 670 } 671 672 // Set state of new participants. 673 if (newParticipantsAdded) { 674 // Set the state of the new participants at once and add to the conference 675 for (ConferenceParticipant newParticipant : newParticipants) { 676 ConferenceParticipantConnection connection = 677 mConferenceParticipantConnections.get(new Pair<>( 678 newParticipant.getHandle(), 679 newParticipant.getEndpoint())); 680 connection.updateState(newParticipant.getState()); 681 } 682 } 683 684 // Finally, remove any participants from the conference that no longer exist in the 685 // conference event package data. 686 Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator = 687 mConferenceParticipantConnections.entrySet().iterator(); 688 while (entryIterator.hasNext()) { 689 Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection> entry = 690 entryIterator.next(); 691 692 if (!participantUserEntities.contains(entry.getKey())) { 693 ConferenceParticipantConnection participant = entry.getValue(); 694 participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 695 participant.removeConnectionListener(mParticipantListener); 696 mTelephonyConnectionService.removeConnection(participant); 697 removeConnection(participant); 698 entryIterator.remove(); 699 oldParticipantsRemoved = true; 700 } 701 } 702 703 // If new participants were added or old ones were removed, we need to ensure the state 704 // of the manage conference capability is updated. 705 if (newParticipantsAdded || oldParticipantsRemoved) { 706 updateManageConference(); 707 } 708 } 709 } 710 711 /** 712 * Creates a new {@link ConferenceParticipantConnection} to represent a 713 * {@link ConferenceParticipant}. 714 * <p> 715 * The new connection is added to the conference controller and connection service. 716 * 717 * @param parent The connection which was notified of the participant change (e.g. the 718 * parent connection). 719 * @param participant The conference participant information. 720 */ 721 private void createConferenceParticipantConnection( 722 TelephonyConnection parent, ConferenceParticipant participant) { 723 724 // Create and add the new connection in holding state so that it does not become the 725 // active call. 726 ConferenceParticipantConnection connection = new ConferenceParticipantConnection( 727 parent.getOriginalConnection(), participant); 728 connection.addConnectionListener(mParticipantListener); 729 connection.setConnectTimeMillis(parent.getConnectTimeMillis()); 730 731 Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s", 732 participant, connection); 733 734 synchronized(mUpdateSyncRoot) { 735 mConferenceParticipantConnections.put(new Pair<>(participant.getHandle(), 736 participant.getEndpoint()), connection); 737 } 738 mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle, 739 connection); 740 addConnection(connection); 741 } 742 743 /** 744 * Removes a conference participant from the conference. 745 * 746 * @param participant The participant to remove. 747 */ 748 private void removeConferenceParticipant(ConferenceParticipantConnection participant) { 749 Log.i(this, "removeConferenceParticipant: %s", participant); 750 751 participant.removeConnectionListener(mParticipantListener); 752 synchronized(mUpdateSyncRoot) { 753 mConferenceParticipantConnections.remove(participant.getUserEntity()); 754 } 755 mTelephonyConnectionService.removeConnection(participant); 756 } 757 758 /** 759 * Disconnects all conference participants from the conference. 760 */ 761 private void disconnectConferenceParticipants() { 762 Log.v(this, "disconnectConferenceParticipants"); 763 764 synchronized(mUpdateSyncRoot) { 765 for (ConferenceParticipantConnection connection : 766 mConferenceParticipantConnections.values()) { 767 768 connection.removeConnectionListener(mParticipantListener); 769 // Mark disconnect cause as cancelled to ensure that the call is not logged in the 770 // call log. 771 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 772 mTelephonyConnectionService.removeConnection(connection); 773 connection.destroy(); 774 } 775 mConferenceParticipantConnections.clear(); 776 } 777 } 778 779 /** 780 * Determines if the passed in participant handle is the same as the conference host's handle. 781 * Starts with a simple equality check. However, the handles from a conference event package 782 * will be a SIP uri, so we need to pull that apart to look for the participant's phone number. 783 * 784 * @param hostHandles The handle(s) of the connection hosting the conference. 785 * @param handle The handle of the conference participant. 786 * @return {@code true} if the host's handle matches the participant's handle, {@code false} 787 * otherwise. 788 */ 789 private boolean isParticipantHost(Uri[] hostHandles, Uri handle) { 790 // If there is no host handle or no participant handle, bail early. 791 if (hostHandles == null || hostHandles.length == 0 || handle == null) { 792 Log.v(this, "isParticipantHost(N) : host or participant uri null"); 793 return false; 794 } 795 796 // Conference event package participants are identified using SIP URIs (see RFC3261). 797 // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers 798 // Per RFC3261, the "user" can be a telephone number. 799 // For example: sip:1650555121;phone-context=blah.com@host.com 800 // In this case, the phone number is in the user field of the URI, and the parameters can be 801 // ignored. 802 // 803 // A SIP URI can also specify a phone number in a format similar to: 804 // sip:+1-212-555-1212@something.com;user=phone 805 // In this case, the phone number is again in user field and the parameters can be ignored. 806 // We can get the user field in these instances by splitting the string on the @, ;, or : 807 // and looking at the first found item. 808 809 String number = handle.getSchemeSpecificPart(); 810 String numberParts[] = number.split("[@;:]"); 811 812 if (numberParts.length == 0) { 813 Log.v(this, "isParticipantHost(N) : no number in participant handle"); 814 return false; 815 } 816 number = numberParts[0]; 817 818 for (Uri hostHandle : hostHandles) { 819 if (hostHandle == null) { 820 continue; 821 } 822 // The host number will be a tel: uri. Per RFC3966, the part after tel: is the phone 823 // number. 824 String hostNumber = hostHandle.getSchemeSpecificPart(); 825 826 // Use a loose comparison of the phone numbers. This ensures that numbers that differ 827 // by special characters are counted as equal. 828 // E.g. +16505551212 would be the same as 16505551212 829 boolean isHost = PhoneNumberUtils.compare(hostNumber, number); 830 831 Log.v(this, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"), 832 Log.pii(hostNumber), Log.pii(number)); 833 834 if (isHost) { 835 return true; 836 } 837 } 838 return false; 839 } 840 841 /** 842 * Handles a change in the original connection backing the conference host connection. This can 843 * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to 844 * GSM or CDMA. 845 * <p> 846 * If this happens, we will add the conference host connection to telecom and tear down the 847 * conference. 848 */ 849 private void handleOriginalConnectionChange() { 850 if (mConferenceHost == null) { 851 Log.w(this, "handleOriginalConnectionChange; conference host missing."); 852 return; 853 } 854 855 com.android.internal.telephony.Connection originalConnection = 856 mConferenceHost.getOriginalConnection(); 857 858 if (originalConnection != null && 859 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 860 Log.i(this, 861 "handleOriginalConnectionChange : handover from IMS connection to " + 862 "new connection: %s", originalConnection); 863 864 PhoneAccountHandle phoneAccountHandle = null; 865 if (mConferenceHost.getPhone() != null) { 866 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 867 Phone imsPhone = mConferenceHost.getPhone(); 868 // The phone account handle for an ImsPhone is based on the default phone (ie 869 // the base GSM or CDMA phone, not on the ImsPhone itself). 870 phoneAccountHandle = 871 PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone()); 872 } else { 873 // In the case of SRVCC, we still need a phone account, so use the top level 874 // phone to create a phone account. 875 phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle( 876 mConferenceHost.getPhone()); 877 } 878 } 879 880 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 881 Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM"); 882 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId()); 883 // This is a newly created conference connection as a result of SRVCC 884 c.setConferenceSupported(true); 885 c.addCapability(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN); 886 c.setConnectionProperties( 887 c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE); 888 c.updateState(); 889 // Copy the connect time from the conferenceHost 890 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis()); 891 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c); 892 mTelephonyConnectionService.addConnectionToConferenceController(c); 893 } // CDMA case not applicable for SRVCC 894 mConferenceHost.removeConnectionListener(mConferenceHostListener); 895 mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener); 896 mConferenceHost = null; 897 setDisconnected(new DisconnectCause(DisconnectCause.OTHER)); 898 disconnectConferenceParticipants(); 899 destroy(); 900 } 901 902 updateStatusHints(); 903 } 904 905 /** 906 * Changes the state of the Ims conference. 907 * 908 * @param state the new state. 909 */ 910 public void setState(int state) { 911 Log.v(this, "setState %s", Connection.stateToString(state)); 912 913 switch (state) { 914 case Connection.STATE_INITIALIZING: 915 case Connection.STATE_NEW: 916 case Connection.STATE_RINGING: 917 // No-op -- not applicable. 918 break; 919 case Connection.STATE_DIALING: 920 setDialing(); 921 break; 922 case Connection.STATE_DISCONNECTED: 923 DisconnectCause disconnectCause; 924 if (mConferenceHost == null) { 925 disconnectCause = new DisconnectCause(DisconnectCause.CANCELED); 926 } else { 927 disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause( 928 mConferenceHost.getOriginalConnection().getDisconnectCause()); 929 } 930 setDisconnected(disconnectCause); 931 disconnectConferenceParticipants(); 932 destroy(); 933 break; 934 case Connection.STATE_ACTIVE: 935 setActive(); 936 break; 937 case Connection.STATE_HOLDING: 938 setOnHold(); 939 break; 940 } 941 } 942 943 private void updateStatusHints() { 944 if (mConferenceHost == null) { 945 setStatusHints(null); 946 return; 947 } 948 949 if (mConferenceHost.isWifi()) { 950 Phone phone = mConferenceHost.getPhone(); 951 if (phone != null) { 952 Context context = phone.getContext(); 953 setStatusHints(new StatusHints( 954 context.getString(R.string.status_hint_label_wifi_call), 955 Icon.createWithResource( 956 context.getResources(), 957 R.drawable.ic_signal_wifi_4_bar_24dp), 958 null /* extras */)); 959 } 960 } else { 961 setStatusHints(null); 962 } 963 } 964 965 /** 966 * Builds a string representation of the {@link ImsConference}. 967 * 968 * @return String representing the conference. 969 */ 970 public String toString() { 971 StringBuilder sb = new StringBuilder(); 972 sb.append("[ImsConference objId:"); 973 sb.append(System.identityHashCode(this)); 974 sb.append(" telecomCallID:"); 975 sb.append(getTelecomCallId()); 976 sb.append(" state:"); 977 sb.append(Connection.stateToString(getState())); 978 sb.append(" hostConnection:"); 979 sb.append(mConferenceHost); 980 sb.append(" participants:"); 981 sb.append(mConferenceParticipantConnections.size()); 982 sb.append("]"); 983 return sb.toString(); 984 } 985 986 private boolean canHoldImsCalls() { 987 PersistableBundle b = getCarrierConfig(); 988 // Return true if the CarrierConfig is unavailable 989 return b == null || b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL); 990 } 991 992 private PersistableBundle getCarrierConfig() { 993 if (mConferenceHost == null) { 994 return null; 995 } 996 997 Phone phone = mConferenceHost.getPhone(); 998 if (phone == null) { 999 return null; 1000 } 1001 return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId()); 1002 } 1003} 1004