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 newParticipantsAdded = false; 972 boolean oldParticipantsRemoved = false; 973 ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size()); 974 HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size()); 975 976 // Determine if the conference event package represents a single party conference. 977 // A single party conference is one where there is no other participant other than the 978 // conference host and one other participant. 979 // We purposely exclude participants which have a disconnected state in the conference 980 // event package; some carriers are known to keep a disconnected participant around in 981 // subsequent CEP updates with a state of disconnected, even though its no longer part 982 // of the conference. 983 // Note: We consider 0 to still be a single party conference since some carriers will 984 // send a conference event package with JUST the host in it when the conference is 985 // disconnected. We don't want to change back to conference mode prior to disconnection 986 // or we will not log the call. 987 boolean isSinglePartyConference = participants.stream() 988 .filter(p -> { 989 Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint()); 990 return !Objects.equals(mHostParticipantIdentity, pIdent) 991 && p.getState() != Connection.STATE_DISCONNECTED; 992 }) 993 .count() <= 1; 994 995 // We will only process the CEP data if: 996 // 1. We're not emulating a single party call. 997 // 2. We're emulating a single party call and the CEP contains more than just the 998 // single party 999 if ((!isMultiparty() && !isSinglePartyConference) 1000 || isMultiparty()) { 1001 // Add any new participants and update existing. 1002 for (ConferenceParticipant participant : participants) { 1003 Pair<Uri, Uri> userEntity = new Pair<>(participant.getHandle(), 1004 participant.getEndpoint()); 1005 1006 // We will exclude disconnected participants from the hash set of tracked 1007 // participants. Some carriers are known to leave disconnected participants in 1008 // the conference event package data which would cause them to be present in the 1009 // conference even though they're disconnected. Removing them from the hash set 1010 // here means we'll clean them up below. 1011 if (participant.getState() != Connection.STATE_DISCONNECTED) { 1012 participantUserEntities.add(userEntity); 1013 } 1014 if (!mConferenceParticipantConnections.containsKey(userEntity)) { 1015 // Some carriers will also include the conference host in the CEP. We will 1016 // filter that out here. 1017 if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) { 1018 createConferenceParticipantConnection(parent, participant); 1019 newParticipants.add(participant); 1020 newParticipantsAdded = true; 1021 } else { 1022 // Track the identity of the conference host; its useful to know when 1023 // we look at the CEP in the future. 1024 mHostParticipantIdentity = userEntity; 1025 } 1026 } else { 1027 ConferenceParticipantConnection connection = 1028 mConferenceParticipantConnections.get(userEntity); 1029 Log.i(this, 1030 "handleConferenceParticipantsUpdate: updateState, participant = %s", 1031 participant); 1032 connection.updateState(participant.getState()); 1033 if (participant.getState() == Connection.STATE_DISCONNECTED) { 1034 /** 1035 * Per {@link ConferenceParticipantConnection#updateState(int)}, we will 1036 * destroy the connection when its disconnected. 1037 */ 1038 handleConnectionDestruction(connection); 1039 } 1040 connection.setVideoState(parent.getVideoState()); 1041 } 1042 } 1043 1044 // Set state of new participants. 1045 if (newParticipantsAdded) { 1046 // Set the state of the new participants at once and add to the conference 1047 for (ConferenceParticipant newParticipant : newParticipants) { 1048 ConferenceParticipantConnection connection = 1049 mConferenceParticipantConnections.get(new Pair<>( 1050 newParticipant.getHandle(), 1051 newParticipant.getEndpoint())); 1052 connection.updateState(newParticipant.getState()); 1053 /** 1054 * Per {@link ConferenceParticipantConnection#updateState(int)}, we will 1055 * destroy the connection when its disconnected. 1056 */ 1057 if (newParticipant.getState() == Connection.STATE_DISCONNECTED) { 1058 handleConnectionDestruction(connection); 1059 } 1060 connection.setVideoState(parent.getVideoState()); 1061 } 1062 } 1063 1064 // Finally, remove any participants from the conference that no longer exist in the 1065 // conference event package data. 1066 Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator = 1067 mConferenceParticipantConnections.entrySet().iterator(); 1068 while (entryIterator.hasNext()) { 1069 Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection> entry = 1070 entryIterator.next(); 1071 1072 if (!participantUserEntities.contains(entry.getKey())) { 1073 ConferenceParticipantConnection participant = entry.getValue(); 1074 participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 1075 removeTelephonyConnection(participant); 1076 participant.destroy(); 1077 entryIterator.remove(); 1078 oldParticipantsRemoved = true; 1079 } 1080 } 1081 } 1082 1083 int newParticipantCount = mConferenceParticipantConnections.size(); 1084 Log.v(this, "handleConferenceParticipantsUpdate: oldParticipantCount=%d, " 1085 + "newParticipantcount=%d", oldParticipantCount, newParticipantCount); 1086 // If the single party call emulation fature flag is enabled, we can potentially treat 1087 // the conference as a single party call when there is just one participant. 1088 if (mFeatureFlagProxy.isUsingSinglePartyCallEmulation() && 1089 !mConferenceHost.isAdhocConferenceCall()) { 1090 if (oldParticipantCount != 1 && newParticipantCount == 1) { 1091 // If number of participants goes to 1, emulate a single party call. 1092 startEmulatingSinglePartyCall(); 1093 } else if (!isMultiparty() && !isSinglePartyConference) { 1094 // Number of participants increased, so stop emulating a single party call. 1095 stopEmulatingSinglePartyCall(); 1096 } 1097 } 1098 1099 // If new participants were added or old ones were removed, we need to ensure the state 1100 // of the manage conference capability is updated. 1101 if (newParticipantsAdded || oldParticipantsRemoved) { 1102 updateManageConference(); 1103 } 1104 1105 // If the conference is empty and we're supposed to do a local disconnect, do so now. 1106 if (mCarrierConfig.shouldLocalDisconnectEmptyConference() 1107 // If we dropped from > 0 participants to zero 1108 // OR if the conference had a single participant and is emulating a standalone 1109 // call. 1110 && (oldParticipantCount > 0 || !isMultiparty()) 1111 // AND the CEP says there is nobody left any more. 1112 && newParticipantCount == 0) { 1113 Log.i(this, "handleConferenceParticipantsUpdate: empty conference; " 1114 + "local disconnect."); 1115 onDisconnect(); 1116 } 1117 } 1118 } 1119 1120 /** 1121 * Called after {@link #startEmulatingSinglePartyCall()} to cause the conference to appear as 1122 * if it is a conference again. 1123 * 1. Tell telecom we're a conference again. 1124 * 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability. 1125 * 3. Null out the name/address. 1126 * 1127 * Note: Single party call emulation is disabled if the conference is taking place via a 1128 * sim call manager. Emulating a single party call requires properties of the conference to be 1129 * changed (connect time, address, conference state) which cannot be guaranteed to be relayed 1130 * correctly by the sim call manager to Telecom. 1131 */ stopEmulatingSinglePartyCall()1132 private void stopEmulatingSinglePartyCall() { 1133 if (mIsUsingSimCallManager) { 1134 Log.i(this, "stopEmulatingSinglePartyCall: using sim call manager; skip."); 1135 return; 1136 } 1137 1138 Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one" 1139 + " participant; make it look conference-like again."); 1140 1141 if (mCouldManageConference) { 1142 int currentCapabilities = getConnectionCapabilities(); 1143 currentCapabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE; 1144 setConnectionCapabilities(currentCapabilities); 1145 } 1146 1147 // Null out the address/name so it doesn't look like a single party call 1148 setAddress(null, TelecomManager.PRESENTATION_UNKNOWN); 1149 setCallerDisplayName(null, TelecomManager.PRESENTATION_UNKNOWN); 1150 1151 // Copy the conference connect time back to the previous lone participant. 1152 ConferenceParticipantConnection loneParticipant = 1153 mConferenceParticipantConnections.get(mLoneParticipantIdentity); 1154 if (loneParticipant != null) { 1155 Log.d(this, 1156 "stopEmulatingSinglePartyCall: restored lone participant connect time"); 1157 loneParticipant.setConnectTimeMillis(getConnectionTime()); 1158 loneParticipant.setConnectionStartElapsedRealtimeMillis( 1159 getConnectionStartElapsedRealtimeMillis()); 1160 } 1161 1162 // Tell Telecom its a conference again. 1163 setConferenceState(true); 1164 } 1165 1166 /** 1167 * Called when a conference drops to a single participant. Causes this conference to present 1168 * itself to Telecom as if it was a single party call. 1169 * 1. Remove the participant from Telecom and from local tracking; when we get a new CEP in 1170 * the future we'll just re-add the participant anyways. 1171 * 2. Tell telecom we're not a conference. 1172 * 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability. 1173 * 4. Set the name/address to that of the single participant. 1174 * 1175 * Note: Single party call emulation is disabled if the conference is taking place via a 1176 * sim call manager. Emulating a single party call requires properties of the conference to be 1177 * changed (connect time, address, conference state) which cannot be guaranteed to be relayed 1178 * correctly by the sim call manager to Telecom. 1179 */ startEmulatingSinglePartyCall()1180 private void startEmulatingSinglePartyCall() { 1181 if (mIsUsingSimCallManager) { 1182 Log.i(this, "startEmulatingSinglePartyCall: using sim call manager; skip."); 1183 return; 1184 } 1185 1186 Log.i(this, "startEmulatingSinglePartyCall: conference has a single " 1187 + "participant; downgrade to single party call."); 1188 1189 Iterator<ConferenceParticipantConnection> valueIterator = 1190 mConferenceParticipantConnections.values().iterator(); 1191 if (valueIterator.hasNext()) { 1192 ConferenceParticipantConnection entry = valueIterator.next(); 1193 1194 // Set the conference name/number to that of the remaining participant. 1195 setAddress(entry.getAddress(), entry.getAddressPresentation()); 1196 setCallerDisplayName(entry.getCallerDisplayName(), 1197 entry.getCallerDisplayNamePresentation()); 1198 setConnectionStartElapsedRealtimeMillis( 1199 entry.getConnectionStartElapsedRealtimeMillis()); 1200 setConnectionTime(entry.getConnectTimeMillis()); 1201 setCallDirection(entry.getCallDirection()); 1202 mLoneParticipantIdentity = new Pair<>(entry.getUserEntity(), entry.getEndpoint()); 1203 1204 // Remove the participant from Telecom. It'll get picked up in a future CEP update 1205 // again anyways. 1206 entry.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED, 1207 DisconnectCause.REASON_EMULATING_SINGLE_CALL)); 1208 removeTelephonyConnection(entry); 1209 entry.destroy(); 1210 valueIterator.remove(); 1211 } 1212 1213 // Have Telecom pretend its not a conference. 1214 setConferenceState(false); 1215 1216 // Remove manage conference capability. 1217 mCouldManageConference = 1218 (getConnectionCapabilities() & Connection.CAPABILITY_MANAGE_CONFERENCE) != 0; 1219 int currentCapabilities = getConnectionCapabilities(); 1220 currentCapabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE; 1221 setConnectionCapabilities(currentCapabilities); 1222 } 1223 1224 /** 1225 * Creates a new {@link ConferenceParticipantConnection} to represent a 1226 * {@link ConferenceParticipant}. 1227 * <p> 1228 * The new connection is added to the conference controller and connection service. 1229 * 1230 * @param parent The connection which was notified of the participant change (e.g. the 1231 * parent connection). 1232 * @param participant The conference participant information. 1233 */ createConferenceParticipantConnection( TelephonyConnection parent, ConferenceParticipant participant)1234 private void createConferenceParticipantConnection( 1235 TelephonyConnection parent, ConferenceParticipant participant) { 1236 1237 // Create and add the new connection in holding state so that it does not become the 1238 // active call. 1239 ConferenceParticipantConnection connection = new ConferenceParticipantConnection( 1240 parent.getOriginalConnection(), participant, 1241 !isConferenceHost() /* isRemotelyHosted */); 1242 1243 if (participant.getConnectTime() == 0) { 1244 connection.setConnectTimeMillis(parent.getConnectTimeMillis()); 1245 connection.setConnectionStartElapsedRealtimeMillis( 1246 parent.getConnectionStartElapsedRealtimeMillis()); 1247 } else { 1248 connection.setConnectTimeMillis(participant.getConnectTime()); 1249 connection.setConnectionStartElapsedRealtimeMillis(participant.getConnectElapsedTime()); 1250 } 1251 // Indicate whether this is an MT or MO call to Telecom; the participant has the cached 1252 // data from the time of merge. 1253 connection.setCallDirection(participant.getCallDirection()); 1254 1255 // Ensure important attributes of the parent get copied to child. 1256 connection.setConnectionProperties(applyHostPropertiesToChild( 1257 connection.getConnectionProperties(), parent.getConnectionProperties())); 1258 connection.setStatusHints(parent.getStatusHints()); 1259 connection.setExtras(getChildExtrasFromHostBundle(parent.getExtras())); 1260 1261 Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s", 1262 participant, connection); 1263 1264 synchronized(mUpdateSyncRoot) { 1265 mConferenceParticipantConnections.put(new Pair<>(participant.getHandle(), 1266 participant.getEndpoint()), connection); 1267 } 1268 1269 mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle, 1270 connection, this); 1271 addTelephonyConnection(connection); 1272 } 1273 1274 /** 1275 * Removes a conference participant from the conference. 1276 * 1277 * @param participant The participant to remove. 1278 */ removeConferenceParticipant(ConferenceParticipantConnection participant)1279 private void removeConferenceParticipant(ConferenceParticipantConnection participant) { 1280 Log.i(this, "removeConferenceParticipant: %s", participant); 1281 1282 synchronized(mUpdateSyncRoot) { 1283 mConferenceParticipantConnections.remove(new Pair<>(participant.getUserEntity(), 1284 participant.getEndpoint())); 1285 } 1286 participant.destroy(); 1287 } 1288 1289 /** 1290 * Disconnects all conference participants from the conference. 1291 */ disconnectConferenceParticipants()1292 private void disconnectConferenceParticipants() { 1293 Log.v(this, "disconnectConferenceParticipants"); 1294 1295 synchronized(mUpdateSyncRoot) { 1296 for (ConferenceParticipantConnection connection : 1297 mConferenceParticipantConnections.values()) { 1298 1299 // Mark disconnect cause as cancelled to ensure that the call is not logged in the 1300 // call log. 1301 connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); 1302 connection.destroy(); 1303 } 1304 mConferenceParticipantConnections.clear(); 1305 updateManageConference(); 1306 } 1307 } 1308 1309 /** 1310 * Extracts a phone number from a {@link Uri}. 1311 * <p> 1312 * Phone numbers can be represented either as a TEL URI or a SIP URI. 1313 * For conference event packages, RFC3261 specifies how participants can be identified using a 1314 * SIP URI. 1315 * A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers 1316 * Per RFC3261, the "user" can be a telephone number. 1317 * For example: sip:1650555121;phone-context=blah.com@host.com 1318 * In this case, the phone number is in the user field of the URI, and the parameters can be 1319 * ignored. 1320 * 1321 * A SIP URI can also specify a phone number in a format similar to: 1322 * sip:+1-212-555-1212@something.com;user=phone 1323 * In this case, the phone number is again in user field and the parameters can be ignored. 1324 * We can get the user field in these instances by splitting the string on the @, ;, or : 1325 * and looking at the first found item. 1326 * @param handle The URI containing a SIP or TEL formatted phone number. 1327 * @return extracted phone number. 1328 */ extractPhoneNumber(@onNull Uri handle)1329 private static @NonNull String extractPhoneNumber(@NonNull Uri handle) { 1330 // Number is always in the scheme specific part, regardless of whether this is a TEL or SIP 1331 // URI. 1332 String number = handle.getSchemeSpecificPart(); 1333 // Get anything before the @ for the SIP case. 1334 String[] numberParts = number.split("[@;:]"); 1335 1336 if (numberParts.length == 0) { 1337 Log.v(LOG_TAG, "extractPhoneNumber(N) : no number in handle"); 1338 return ""; 1339 } 1340 return numberParts[0]; 1341 } 1342 1343 /** 1344 * Determines if the passed in participant handle is the same as the conference host's handle. 1345 * Starts with a simple equality check. However, the handles from a conference event package 1346 * will be a SIP uri, so we need to pull that apart to look for the participant's phone number. 1347 * 1348 * @param hostHandles The handle(s) of the connection hosting the conference, typically obtained 1349 * from P-Associated-Uri entries. 1350 * @param handle The handle of the conference participant. 1351 * @return {@code true} if the host's handle matches the participant's handle, {@code false} 1352 * otherwise. 1353 */ 1354 @VisibleForTesting isParticipantHost(Uri[] hostHandles, Uri handle)1355 public static boolean isParticipantHost(Uri[] hostHandles, Uri handle) { 1356 // If there is no host handle or no participant handle, bail early. 1357 if (hostHandles == null || hostHandles.length == 0 || handle == null) { 1358 Log.v(LOG_TAG, "isParticipantHost(N) : host or participant uri null"); 1359 return false; 1360 } 1361 1362 String number = extractPhoneNumber(handle); 1363 // If we couldn't extract the participant's number, then we can't determine if it is the 1364 // host or not. 1365 if (TextUtils.isEmpty(number)) { 1366 return false; 1367 } 1368 1369 for (Uri hostHandle : hostHandles) { 1370 if (hostHandle == null) { 1371 continue; 1372 } 1373 // Similar to the CEP participant data, the host identity in the P-Associated-Uri could 1374 // be a SIP URI or a TEL URI. 1375 String hostNumber = extractPhoneNumber(hostHandle); 1376 1377 // Use a loose comparison of the phone numbers. This ensures that numbers that differ 1378 // by special characters are counted as equal. 1379 // E.g. +16505551212 would be the same as 16505551212 1380 boolean isHost = PhoneNumberUtils.compare(hostNumber, number); 1381 1382 Log.v(LOG_TAG, "isParticipantHost(%s) : host: %s, participant %s", (isHost ? "Y" : "N"), 1383 Rlog.pii(LOG_TAG, hostNumber), Rlog.pii(LOG_TAG, number)); 1384 1385 if (isHost) { 1386 return true; 1387 } 1388 } 1389 return false; 1390 } 1391 1392 /** 1393 * Handles a change in the original connection backing the conference host connection. This can 1394 * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to 1395 * GSM or CDMA. 1396 * <p> 1397 * If this happens, we will add the conference host connection to telecom and tear down the 1398 * conference. 1399 */ handleOriginalConnectionChange()1400 private void handleOriginalConnectionChange() { 1401 if (mConferenceHost == null) { 1402 Log.w(this, "handleOriginalConnectionChange; conference host missing."); 1403 return; 1404 } 1405 1406 com.android.internal.telephony.Connection originalConnection = 1407 mConferenceHost.getOriginalConnection(); 1408 1409 if (originalConnection != null && 1410 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 1411 Log.i(this, 1412 "handleOriginalConnectionChange : handover from IMS connection to " + 1413 "new connection: %s", originalConnection); 1414 1415 PhoneAccountHandle phoneAccountHandle = null; 1416 if (mConferenceHost.getPhone() != null) { 1417 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) { 1418 Phone imsPhone = mConferenceHost.getPhone(); 1419 // The phone account handle for an ImsPhone is based on the default phone (ie 1420 // the base GSM or CDMA phone, not on the ImsPhone itself). 1421 phoneAccountHandle = 1422 PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone()); 1423 } else { 1424 // In the case of SRVCC, we still need a phone account, so use the top level 1425 // phone to create a phone account. 1426 phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle( 1427 mConferenceHost.getPhone()); 1428 } 1429 } 1430 1431 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 1432 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId(), 1433 mConferenceHost.getCallDirection()); 1434 Log.i(this, "handleOriginalConnectionChange : SRVCC to GSM." 1435 + " Created new GsmConnection with objId=" + System.identityHashCode(c) 1436 + " and originalConnection objId=" 1437 + System.identityHashCode(originalConnection)); 1438 // This is a newly created conference connection as a result of SRVCC 1439 c.setConferenceSupported(true); 1440 c.setTelephonyConnectionProperties( 1441 c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE); 1442 c.updateState(); 1443 // Copy the connect time from the conferenceHost 1444 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis()); 1445 c.setConnectionStartElapsedRealtimeMillis( 1446 mConferenceHost.getConnectionStartElapsedRealtimeMillis()); 1447 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c); 1448 mTelephonyConnectionService.addConnectionToConferenceController(c); 1449 } // CDMA case not applicable for SRVCC 1450 mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener); 1451 mConferenceHost = null; 1452 setDisconnected(new DisconnectCause(DisconnectCause.OTHER)); 1453 disconnectConferenceParticipants(); 1454 destroyTelephonyConference(); 1455 } 1456 1457 updateStatusHints(); 1458 } 1459 1460 /** 1461 * Changes the state of the Ims conference. 1462 * 1463 * @param state the new state. 1464 */ setState(int state)1465 public void setState(int state) { 1466 Log.v(this, "setState %s", Connection.stateToString(state)); 1467 1468 switch (state) { 1469 case Connection.STATE_INITIALIZING: 1470 case Connection.STATE_NEW: 1471 // No-op -- not applicable. 1472 break; 1473 case Connection.STATE_RINGING: 1474 setConferenceOnRinging(); 1475 break; 1476 case Connection.STATE_DIALING: 1477 setConferenceOnDialing(); 1478 break; 1479 case Connection.STATE_DISCONNECTED: 1480 DisconnectCause disconnectCause; 1481 if (mConferenceHost == null) { 1482 disconnectCause = new DisconnectCause(DisconnectCause.CANCELED); 1483 } else { 1484 if (mConferenceHost.getPhone() != null) { 1485 disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause( 1486 mConferenceHost.getOriginalConnection().getDisconnectCause(), 1487 null, mConferenceHost.getPhone().getPhoneId()); 1488 } else { 1489 disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause( 1490 mConferenceHost.getOriginalConnection().getDisconnectCause()); 1491 } 1492 } 1493 setDisconnected(disconnectCause); 1494 disconnectConferenceParticipants(); 1495 destroyTelephonyConference(); 1496 break; 1497 case Connection.STATE_ACTIVE: 1498 setConferenceOnActive(); 1499 break; 1500 case Connection.STATE_HOLDING: 1501 setConferenceOnHold(); 1502 break; 1503 } 1504 } 1505 1506 /** 1507 * Determines if the host of this conference is capable of video calling. 1508 * @return {@code true} if video capable, {@code false} otherwise. 1509 */ isVideoCapable()1510 private boolean isVideoCapable() { 1511 int capabilities = mConferenceHost.getConnectionCapabilities(); 1512 return (capabilities & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL) != 0 1513 && (capabilities & Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL) != 0; 1514 } 1515 updateStatusHints()1516 private void updateStatusHints() { 1517 if (mConferenceHost == null) { 1518 setStatusHints(null); 1519 return; 1520 } 1521 1522 if (mConferenceHost.isWifi()) { 1523 Phone phone = mConferenceHost.getPhone(); 1524 if (phone != null) { 1525 Context context = phone.getContext(); 1526 StatusHints hints = new StatusHints( 1527 context.getString(R.string.status_hint_label_wifi_call), 1528 Icon.createWithResource( 1529 context, R.drawable.ic_signal_wifi_4_bar_24dp), 1530 null /* extras */); 1531 setStatusHints(hints); 1532 1533 // Ensure the children know they're a WIFI call as well. 1534 for (Connection c : getConnections()) { 1535 c.setStatusHints(hints); 1536 } 1537 } 1538 } else { 1539 setStatusHints(null); 1540 } 1541 } 1542 1543 /** 1544 * Updates the conference's properties based on changes to the host. 1545 * Also ensures pertinent properties from the host such as the WIFI property are copied to the 1546 * children as well. 1547 * @param connectionProperties The new host properties. 1548 */ updateConnectionProperties(int connectionProperties)1549 private void updateConnectionProperties(int connectionProperties) { 1550 int properties = ImsConference.this.getConnectionProperties(); 1551 setConnectionProperties(applyHostProperties(properties, connectionProperties)); 1552 1553 for (Connection c : getConnections()) { 1554 c.setConnectionProperties(applyHostPropertiesToChild(c.getConnectionProperties(), 1555 connectionProperties)); 1556 } 1557 } 1558 1559 /** 1560 * Updates extras in the conference based on changes made in the parent. 1561 * Also copies select extras (e.g. EXTRA_CALL_NETWORK_TYPE) to the children as well. 1562 * @param extras The extras to copy. 1563 */ updateExtras(Bundle extras)1564 private void updateExtras(Bundle extras) { 1565 putExtras(extras); 1566 1567 if (extras == null) { 1568 return; 1569 } 1570 1571 Bundle childBundle = getChildExtrasFromHostBundle(extras); 1572 for (Connection c : getConnections()) { 1573 c.putExtras(childBundle); 1574 } 1575 } 1576 1577 /** 1578 * Given an extras bundle from the host, returns a new bundle containing those extras which are 1579 * releveant to the children. 1580 * @param extras The host extras. 1581 * @return The extras pertinent to the children. 1582 */ getChildExtrasFromHostBundle(Bundle extras)1583 private Bundle getChildExtrasFromHostBundle(Bundle extras) { 1584 Bundle extrasToCopy = new Bundle(); 1585 if (extras != null && extras.containsKey(TelecomManager.EXTRA_CALL_NETWORK_TYPE)) { 1586 int networkType = extras.getInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE); 1587 extrasToCopy.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE, networkType); 1588 } 1589 return extrasToCopy; 1590 } 1591 1592 /** 1593 * Given the properties from a conference host applies and changes to the host's properties to 1594 * the child as well. 1595 * @param childProperties The existing child properties. 1596 * @param hostProperties The properties from the host. 1597 * @return The child properties with the applicable host bits set/unset. 1598 */ applyHostPropertiesToChild(int childProperties, int hostProperties)1599 private int applyHostPropertiesToChild(int childProperties, int hostProperties) { 1600 childProperties = changeBitmask(childProperties, 1601 Connection.PROPERTY_WIFI, 1602 (hostProperties & Connection.PROPERTY_WIFI) != 0); 1603 return childProperties; 1604 } 1605 1606 /** 1607 * Builds a string representation of the {@link ImsConference}. 1608 * 1609 * @return String representing the conference. 1610 */ toString()1611 public String toString() { 1612 StringBuilder sb = new StringBuilder(); 1613 sb.append("[ImsConference objId:"); 1614 sb.append(System.identityHashCode(this)); 1615 sb.append(" telecomCallID:"); 1616 sb.append(getTelecomCallId()); 1617 sb.append(" state:"); 1618 sb.append(Connection.stateToString(getState())); 1619 sb.append(" hostConnection:"); 1620 sb.append(mConferenceHost); 1621 sb.append(" participants:"); 1622 sb.append(mConferenceParticipantConnections.size()); 1623 sb.append("]"); 1624 return sb.toString(); 1625 } 1626 1627 /** 1628 * @return The number of participants in the conference. 1629 */ getNumberOfParticipants()1630 public int getNumberOfParticipants() { 1631 return mConferenceParticipantConnections.size(); 1632 } 1633 1634 /** 1635 * @return {@code True} if the carrier enforces a maximum conference size, and the number of 1636 * participants in the conference has reached the limit, {@code false} otherwise. 1637 */ isFullConference()1638 public boolean isFullConference() { 1639 return mCarrierConfig.isMaximumConferenceSizeEnforced() 1640 && getNumberOfParticipants() >= mCarrierConfig.getMaximumConferenceSize(); 1641 } 1642 1643 /** 1644 * Handles destruction of a {@link ConferenceParticipantConnection}. 1645 * We remove the participant from the list of tracked participants in the conference and 1646 * update whether the conference can be managed. 1647 * @param participant the conference participant. 1648 */ handleConnectionDestruction(ConferenceParticipantConnection participant)1649 private void handleConnectionDestruction(ConferenceParticipantConnection participant) { 1650 removeConferenceParticipant(participant); 1651 updateManageConference(); 1652 } 1653 } 1654