ImsConference.java revision 34bd26d41700bcc083486a217d4982f6e62d733b
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 conferenceCapabilities = changeBitmask(conferenceCapabilities, 334 Connection.CAPABILITY_CAN_PAUSE_VIDEO, 335 mConferenceHost.getVideoPauseSupported() && isVideoCapable()); 336 337 return conferenceCapabilities; 338 } 339 340 /** 341 * Transfers properties from the conference host to the conference itself. 342 * 343 * @param conferenceProperties The current conference properties. 344 * @param properties The new conference host properties. 345 * @return The merged properties to be applied to the conference. 346 */ 347 private int applyHostProperties(int conferenceProperties, int properties) { 348 conferenceProperties = changeBitmask(conferenceProperties, 349 Connection.PROPERTY_HIGH_DEF_AUDIO, 350 can(properties, Connection.PROPERTY_HIGH_DEF_AUDIO)); 351 352 conferenceProperties = changeBitmask(conferenceProperties, 353 Connection.PROPERTY_WIFI, 354 can(properties, Connection.PROPERTY_WIFI)); 355 356 conferenceProperties = changeBitmask(conferenceProperties, 357 Connection.PROPERTY_IS_EXTERNAL_CALL, 358 can(properties, Connection.PROPERTY_IS_EXTERNAL_CALL)); 359 360 return conferenceProperties; 361 } 362 363 /** 364 * Not used by the IMS conference controller. 365 * 366 * @return {@code Null}. 367 */ 368 @Override 369 public android.telecom.Connection getPrimaryConnection() { 370 return null; 371 } 372 373 /** 374 * Returns VideoProvider of the conference. This can be null. 375 * 376 * @hide 377 */ 378 @Override 379 public VideoProvider getVideoProvider() { 380 if (mConferenceHost != null) { 381 return mConferenceHost.getVideoProvider(); 382 } 383 return null; 384 } 385 386 /** 387 * Returns video state of conference 388 * 389 * @hide 390 */ 391 @Override 392 public int getVideoState() { 393 if (mConferenceHost != null) { 394 return mConferenceHost.getVideoState(); 395 } 396 return VideoProfile.STATE_AUDIO_ONLY; 397 } 398 399 /** 400 * Invoked when the Conference and all its {@link Connection}s should be disconnected. 401 * <p> 402 * Hangs up the call via the conference host connection. When the host connection has been 403 * successfully disconnected, the {@link #mConferenceHostListener} listener receives an 404 * {@code onDestroyed} event, which triggers the conference participant connections to be 405 * disconnected. 406 */ 407 @Override 408 public void onDisconnect() { 409 Log.v(this, "onDisconnect: hanging up conference host."); 410 if (mConferenceHost == null) { 411 return; 412 } 413 414 Call call = mConferenceHost.getCall(); 415 if (call != null) { 416 try { 417 call.hangup(); 418 } catch (CallStateException e) { 419 Log.e(this, e, "Exception thrown trying to hangup conference"); 420 } 421 } 422 } 423 424 /** 425 * Invoked when the specified {@link android.telecom.Connection} should be separated from the 426 * conference call. 427 * <p> 428 * IMS does not support separating connections from the conference. 429 * 430 * @param connection The connection to separate. 431 */ 432 @Override 433 public void onSeparate(android.telecom.Connection connection) { 434 Log.wtf(this, "Cannot separate connections from an IMS conference."); 435 } 436 437 /** 438 * Invoked when the specified {@link android.telecom.Connection} should be merged into the 439 * conference call. 440 * 441 * @param connection The {@code Connection} to merge. 442 */ 443 @Override 444 public void onMerge(android.telecom.Connection connection) { 445 try { 446 Phone phone = mConferenceHost.getPhone(); 447 if (phone != null) { 448 phone.conference(); 449 } 450 } catch (CallStateException e) { 451 Log.e(this, e, "Exception thrown trying to merge call into a conference"); 452 } 453 } 454 455 /** 456 * Invoked when the conference should be put on hold. 457 */ 458 @Override 459 public void onHold() { 460 if (mConferenceHost == null) { 461 return; 462 } 463 mConferenceHost.performHold(); 464 } 465 466 /** 467 * Invoked when the conference should be moved from hold to active. 468 */ 469 @Override 470 public void onUnhold() { 471 if (mConferenceHost == null) { 472 return; 473 } 474 mConferenceHost.performUnhold(); 475 } 476 477 /** 478 * Invoked to play a DTMF tone. 479 * 480 * @param c A DTMF character. 481 */ 482 @Override 483 public void onPlayDtmfTone(char c) { 484 if (mConferenceHost == null) { 485 return; 486 } 487 mConferenceHost.onPlayDtmfTone(c); 488 } 489 490 /** 491 * Invoked to stop playing a DTMF tone. 492 */ 493 @Override 494 public void onStopDtmfTone() { 495 if (mConferenceHost == null) { 496 return; 497 } 498 mConferenceHost.onStopDtmfTone(); 499 } 500 501 /** 502 * Handles the addition of connections to the {@link ImsConference}. The 503 * {@link ImsConferenceController} does not add connections to the conference. 504 * 505 * @param connection The newly added connection. 506 */ 507 @Override 508 public void onConnectionAdded(android.telecom.Connection connection) { 509 // No-op 510 } 511 512 /** 513 * Changes a bit-mask to add or remove a bit-field. 514 * 515 * @param bitmask The bit-mask. 516 * @param bitfield The bit-field to change. 517 * @param enabled Whether the bit-field should be set or removed. 518 * @return The bit-mask with the bit-field changed. 519 */ 520 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 521 if (enabled) { 522 return bitmask | bitfield; 523 } else { 524 return bitmask & ~bitfield; 525 } 526 } 527 528 /** 529 * Determines if this conference is hosted on the current device or the peer device. 530 * 531 * @return {@code true} if this conference is hosted on the current device, {@code false} if it 532 * is hosted on the peer device. 533 */ 534 public boolean isConferenceHost() { 535 if (mConferenceHost == null) { 536 return false; 537 } 538 com.android.internal.telephony.Connection originalConnection = 539 mConferenceHost.getOriginalConnection(); 540 541 return originalConnection != null && originalConnection.isMultiparty() && 542 originalConnection.isConferenceHost(); 543 } 544 545 /** 546 * Updates the manage conference capability of the conference. Where there are one or more 547 * conference event package participants, the conference management is permitted. Where there 548 * are no conference event package participants, conference management is not permitted. 549 * <p> 550 * Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure 551 * that the conference is represented appropriately on Bluetooth devices. 552 */ 553 private void updateManageConference() { 554 boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE); 555 boolean canManageConference = !mConferenceParticipantConnections.isEmpty(); 556 Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N", 557 canManageConference ? "Y" : "N"); 558 559 if (couldManageConference != canManageConference) { 560 int capabilities = getConnectionCapabilities(); 561 562 if (canManageConference) { 563 capabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE; 564 capabilities &= ~Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN; 565 } else { 566 capabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE; 567 capabilities |= Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN; 568 } 569 570 setConnectionCapabilities(capabilities); 571 } 572 } 573 574 /** 575 * Sets the connection hosting the conference and registers for callbacks. 576 * 577 * @param conferenceHost The connection hosting the conference. 578 */ 579 private void setConferenceHost(TelephonyConnection conferenceHost) { 580 if (Log.VERBOSE) { 581 Log.v(this, "setConferenceHost " + conferenceHost); 582 } 583 584 mConferenceHost = conferenceHost; 585 586 // Attempt to get the conference host's address (e.g. the host's own phone number). 587 // We need to look at the default phone for the ImsPhone when creating the phone account 588 // for the 589 if (mConferenceHost.getPhone() != null && 590 mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 591 // Look up the conference host's address; we need this later for filtering out the 592 // conference host in conference event package data. 593 Phone imsPhone = mConferenceHost.getPhone(); 594 mConferenceHostPhoneAccountHandle = 595 PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone()); 596 Uri hostAddress = TelecomAccountRegistry.getInstance(mTelephonyConnectionService) 597 .getAddress(mConferenceHostPhoneAccountHandle); 598 599 ArrayList<Uri> hostAddresses = new ArrayList<>(); 600 601 // add address from TelecomAccountRegistry 602 if (hostAddress != null) { 603 hostAddresses.add(hostAddress); 604 } 605 606 // add addresses from phone 607 if (imsPhone.getCurrentSubscriberUris() != null) { 608 hostAddresses.addAll( 609 new ArrayList<>(Arrays.asList(imsPhone.getCurrentSubscriberUris()))); 610 } 611 612 mConferenceHostAddress = new Uri[hostAddresses.size()]; 613 mConferenceHostAddress = hostAddresses.toArray(mConferenceHostAddress); 614 } 615 616 mConferenceHost.addConnectionListener(mConferenceHostListener); 617 mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener); 618 setConnectionCapabilities(applyHostCapabilities(getConnectionCapabilities(), 619 mConferenceHost.getConnectionCapabilities(), 620 mConferenceHost.isCarrierVideoConferencingSupported())); 621 setConnectionProperties(applyHostProperties(getConnectionProperties(), 622 mConferenceHost.getConnectionProperties())); 623 624 setState(mConferenceHost.getState()); 625 updateStatusHints(); 626 } 627 628 /** 629 * Handles state changes for conference participant(s). The participants data passed in 630 * 631 * @param parent The connection which was notified of the conference participant. 632 * @param participants The conference participant information. 633 */ 634 private void handleConferenceParticipantsUpdate( 635 TelephonyConnection parent, List<ConferenceParticipant> participants) { 636 637 if (participants == null) { 638 return; 639 } 640 641 Log.i(this, "handleConferenceParticipantsUpdate: size=%d", participants.size()); 642 643 // Perform the update in a synchronized manner. It is possible for the IMS framework to 644 // trigger two onConferenceParticipantsChanged callbacks in quick succession. If the first 645 // update adds new participants, and the second does something like update the status of one 646 // of the participants, we can get into a situation where the participant is added twice. 647 synchronized (mUpdateSyncRoot) { 648 boolean newParticipantsAdded = false; 649 boolean oldParticipantsRemoved = false; 650 ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size()); 651 HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size()); 652 653 // Add any new participants and update existing. 654 for (ConferenceParticipant participant : participants) { 655 Pair<Uri,Uri> userEntity = new Pair<>(participant.getHandle(), 656 participant.getEndpoint()); 657 658 participantUserEntities.add(userEntity); 659 if (!mConferenceParticipantConnections.containsKey(userEntity)) { 660 // Some carriers will also include the conference host in the CEP. We will 661 // filter that out here. 662 if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) { 663 createConferenceParticipantConnection(parent, participant); 664 newParticipants.add(participant); 665 newParticipantsAdded = true; 666 } 667 } else { 668 ConferenceParticipantConnection connection = 669 mConferenceParticipantConnections.get(userEntity); 670 Log.i(this, "handleConferenceParticipantsUpdate: updateState, participant = %s", 671 participant); 672 connection.updateState(participant.getState()); 673 } 674 } 675 676 // Set state of new participants. 677 if (newParticipantsAdded) { 678 // Set the state of the new participants at once and add to the conference 679 for (ConferenceParticipant newParticipant : newParticipants) { 680 ConferenceParticipantConnection connection = 681 mConferenceParticipantConnections.get(new Pair<>( 682 newParticipant.getHandle(), 683 newParticipant.getEndpoint())); 684 connection.updateState(newParticipant.getState()); 685 } 686 } 687 688 // Finally, remove any participants from the conference that no longer exist in the 689 // conference event package data. 690 Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator = 691 mConferenceParticipantConnections.entrySet().iterator(); 692 while (entryIterator.hasNext()) { 693 Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection> entry = 694 entryIterator.next(); 695 696 if (!participantUserEntities.contains(entry.getKey())) { 697 ConferenceParticipantConnection participant = entry.getValue(); 698 participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 699 participant.removeConnectionListener(mParticipantListener); 700 mTelephonyConnectionService.removeConnection(participant); 701 removeConnection(participant); 702 entryIterator.remove(); 703 oldParticipantsRemoved = true; 704 } 705 } 706 707 // If new participants were added or old ones were removed, we need to ensure the state 708 // of the manage conference capability is updated. 709 if (newParticipantsAdded || oldParticipantsRemoved) { 710 updateManageConference(); 711 } 712 } 713 } 714 715 /** 716 * Creates a new {@link ConferenceParticipantConnection} to represent a 717 * {@link ConferenceParticipant}. 718 * <p> 719 * The new connection is added to the conference controller and connection service. 720 * 721 * @param parent The connection which was notified of the participant change (e.g. the 722 * parent connection). 723 * @param participant The conference participant information. 724 */ 725 private void createConferenceParticipantConnection( 726 TelephonyConnection parent, ConferenceParticipant participant) { 727 728 // Create and add the new connection in holding state so that it does not become the 729 // active call. 730 ConferenceParticipantConnection connection = new ConferenceParticipantConnection( 731 parent.getOriginalConnection(), participant); 732 connection.addConnectionListener(mParticipantListener); 733 connection.setConnectTimeMillis(parent.getConnectTimeMillis()); 734 735 Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s", 736 participant, connection); 737 738 synchronized(mUpdateSyncRoot) { 739 mConferenceParticipantConnections.put(new Pair<>(participant.getHandle(), 740 participant.getEndpoint()), connection); 741 } 742 mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle, 743 connection); 744 addConnection(connection); 745 } 746 747 /** 748 * Removes a conference participant from the conference. 749 * 750 * @param participant The participant to remove. 751 */ 752 private void removeConferenceParticipant(ConferenceParticipantConnection participant) { 753 Log.i(this, "removeConferenceParticipant: %s", participant); 754 755 participant.removeConnectionListener(mParticipantListener); 756 synchronized(mUpdateSyncRoot) { 757 mConferenceParticipantConnections.remove(participant.getUserEntity()); 758 } 759 mTelephonyConnectionService.removeConnection(participant); 760 } 761 762 /** 763 * Disconnects all conference participants from the conference. 764 */ 765 private void disconnectConferenceParticipants() { 766 Log.v(this, "disconnectConferenceParticipants"); 767 768 synchronized(mUpdateSyncRoot) { 769 for (ConferenceParticipantConnection connection : 770 mConferenceParticipantConnections.values()) { 771 772 connection.removeConnectionListener(mParticipantListener); 773 // Mark disconnect cause as cancelled to ensure that the call is not logged in the 774 // call log. 775 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 776 mTelephonyConnectionService.removeConnection(connection); 777 connection.destroy(); 778 } 779 mConferenceParticipantConnections.clear(); 780 } 781 } 782 783 /** 784 * Determines if the passed in participant handle is the same as the conference host's handle. 785 * Starts with a simple equality check. However, the handles from a conference event package 786 * will be a SIP uri, so we need to pull that apart to look for the participant's phone number. 787 * 788 * @param hostHandles The handle(s) of the connection hosting the conference. 789 * @param handle The handle of the conference participant. 790 * @return {@code true} if the host's handle matches the participant's handle, {@code false} 791 * otherwise. 792 */ 793 private boolean isParticipantHost(Uri[] hostHandles, Uri handle) { 794 // If there is no host handle or no participant handle, bail early. 795 if (hostHandles == null || hostHandles.length == 0 || handle == null) { 796 Log.v(this, "isParticipantHost(N) : host or participant uri null"); 797 return false; 798 } 799 800 // Conference event package participants are identified using SIP URIs (see RFC3261). 801 // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers 802 // Per RFC3261, the "user" can be a telephone number. 803 // For example: sip:1650555121;phone-context=blah.com@host.com 804 // In this case, the phone number is in the user field of the URI, and the parameters can be 805 // ignored. 806 // 807 // A SIP URI can also specify a phone number in a format similar to: 808 // sip:+1-212-555-1212@something.com;user=phone 809 // In this case, the phone number is again in user field and the parameters can be ignored. 810 // We can get the user field in these instances by splitting the string on the @, ;, or : 811 // and looking at the first found item. 812 813 String number = handle.getSchemeSpecificPart(); 814 String numberParts[] = number.split("[@;:]"); 815 816 if (numberParts.length == 0) { 817 Log.v(this, "isParticipantHost(N) : no number in participant handle"); 818 return false; 819 } 820 number = numberParts[0]; 821 822 for (Uri hostHandle : hostHandles) { 823 if (hostHandle == null) { 824 continue; 825 } 826 // The host number will be a tel: uri. Per RFC3966, the part after tel: is the phone 827 // number. 828 String hostNumber = hostHandle.getSchemeSpecificPart(); 829 830 // Use a loose comparison of the phone numbers. This ensures that numbers that differ 831 // by special characters are counted as equal. 832 // E.g. +16505551212 would be the same as 16505551212 833 boolean isHost = PhoneNumberUtils.compare(hostNumber, number); 834 835 Log.v(this, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"), 836 Log.pii(hostNumber), Log.pii(number)); 837 838 if (isHost) { 839 return true; 840 } 841 } 842 return false; 843 } 844 845 /** 846 * Handles a change in the original connection backing the conference host connection. This can 847 * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to 848 * GSM or CDMA. 849 * <p> 850 * If this happens, we will add the conference host connection to telecom and tear down the 851 * conference. 852 */ 853 private void handleOriginalConnectionChange() { 854 if (mConferenceHost == null) { 855 Log.w(this, "handleOriginalConnectionChange; conference host missing."); 856 return; 857 } 858 859 com.android.internal.telephony.Connection originalConnection = 860 mConferenceHost.getOriginalConnection(); 861 862 if (originalConnection != null && 863 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 864 Log.i(this, 865 "handleOriginalConnectionChange : handover from IMS connection to " + 866 "new connection: %s", originalConnection); 867 868 PhoneAccountHandle phoneAccountHandle = null; 869 if (mConferenceHost.getPhone() != null) { 870 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 871 Phone imsPhone = mConferenceHost.getPhone(); 872 // The phone account handle for an ImsPhone is based on the default phone (ie 873 // the base GSM or CDMA phone, not on the ImsPhone itself). 874 phoneAccountHandle = 875 PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone()); 876 } else { 877 // In the case of SRVCC, we still need a phone account, so use the top level 878 // phone to create a phone account. 879 phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle( 880 mConferenceHost.getPhone()); 881 } 882 } 883 884 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 885 Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM"); 886 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId()); 887 // This is a newly created conference connection as a result of SRVCC 888 c.setConferenceSupported(true); 889 c.addCapability(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN); 890 c.setConnectionProperties( 891 c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE); 892 c.updateState(); 893 // Copy the connect time from the conferenceHost 894 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis()); 895 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c); 896 mTelephonyConnectionService.addConnectionToConferenceController(c); 897 } // CDMA case not applicable for SRVCC 898 mConferenceHost.removeConnectionListener(mConferenceHostListener); 899 mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener); 900 mConferenceHost = null; 901 setDisconnected(new DisconnectCause(DisconnectCause.OTHER)); 902 disconnectConferenceParticipants(); 903 destroy(); 904 } 905 906 updateStatusHints(); 907 } 908 909 /** 910 * Changes the state of the Ims conference. 911 * 912 * @param state the new state. 913 */ 914 public void setState(int state) { 915 Log.v(this, "setState %s", Connection.stateToString(state)); 916 917 switch (state) { 918 case Connection.STATE_INITIALIZING: 919 case Connection.STATE_NEW: 920 case Connection.STATE_RINGING: 921 // No-op -- not applicable. 922 break; 923 case Connection.STATE_DIALING: 924 setDialing(); 925 break; 926 case Connection.STATE_DISCONNECTED: 927 DisconnectCause disconnectCause; 928 if (mConferenceHost == null) { 929 disconnectCause = new DisconnectCause(DisconnectCause.CANCELED); 930 } else { 931 disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause( 932 mConferenceHost.getOriginalConnection().getDisconnectCause()); 933 } 934 setDisconnected(disconnectCause); 935 disconnectConferenceParticipants(); 936 destroy(); 937 break; 938 case Connection.STATE_ACTIVE: 939 setActive(); 940 break; 941 case Connection.STATE_HOLDING: 942 setOnHold(); 943 break; 944 } 945 } 946 947 /** 948 * Determines if the host of this conference is capable of video calling. 949 * @return {@code true} if video capable, {@code false} otherwise. 950 */ 951 private boolean isVideoCapable() { 952 int capabilities = mConferenceHost.getConnectionCapabilities(); 953 return can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 954 && can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 955 } 956 957 private void updateStatusHints() { 958 if (mConferenceHost == null) { 959 setStatusHints(null); 960 return; 961 } 962 963 if (mConferenceHost.isWifi()) { 964 Phone phone = mConferenceHost.getPhone(); 965 if (phone != null) { 966 Context context = phone.getContext(); 967 setStatusHints(new StatusHints( 968 context.getString(R.string.status_hint_label_wifi_call), 969 Icon.createWithResource( 970 context.getResources(), 971 R.drawable.ic_signal_wifi_4_bar_24dp), 972 null /* extras */)); 973 } 974 } else { 975 setStatusHints(null); 976 } 977 } 978 979 /** 980 * Builds a string representation of the {@link ImsConference}. 981 * 982 * @return String representing the conference. 983 */ 984 public String toString() { 985 StringBuilder sb = new StringBuilder(); 986 sb.append("[ImsConference objId:"); 987 sb.append(System.identityHashCode(this)); 988 sb.append(" telecomCallID:"); 989 sb.append(getTelecomCallId()); 990 sb.append(" state:"); 991 sb.append(Connection.stateToString(getState())); 992 sb.append(" hostConnection:"); 993 sb.append(mConferenceHost); 994 sb.append(" participants:"); 995 sb.append(mConferenceParticipantConnections.size()); 996 sb.append("]"); 997 return sb.toString(); 998 } 999 1000 private boolean canHoldImsCalls() { 1001 PersistableBundle b = getCarrierConfig(); 1002 // Return true if the CarrierConfig is unavailable 1003 return b == null || b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL); 1004 } 1005 1006 private PersistableBundle getCarrierConfig() { 1007 if (mConferenceHost == null) { 1008 return null; 1009 } 1010 1011 Phone phone = mConferenceHost.getPhone(); 1012 if (phone == null) { 1013 return null; 1014 } 1015 return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId()); 1016 } 1017} 1018