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