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