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