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