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