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