TelephonyConnection.java revision 13ae12c2b4519b28ad25ceb699545991199c0e11
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 17package com.android.services.telephony; 18 19import android.content.Context; 20import android.graphics.drawable.Icon; 21import android.net.Uri; 22import android.os.AsyncResult; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.telecom.CallAudioState; 27import android.telecom.ConferenceParticipant; 28import android.telecom.Connection; 29import android.telecom.PhoneAccount; 30import android.telecom.PhoneAccountHandle; 31import android.telecom.StatusHints; 32import android.telecom.TelecomManager; 33import android.telecom.VideoProfile; 34import android.telephony.PhoneNumberUtils; 35import android.util.Pair; 36 37import com.android.ims.ImsCallProfile; 38import com.android.internal.telephony.Call; 39import com.android.internal.telephony.CallStateException; 40import com.android.internal.telephony.Connection.Capability; 41import com.android.internal.telephony.Connection.PostDialListener; 42import com.android.internal.telephony.PhoneConstants; 43import com.android.internal.telephony.gsm.SuppServiceNotification; 44 45import com.android.internal.telephony.Phone; 46import com.android.internal.telephony.imsphone.ImsPhone; 47import com.android.phone.PhoneUtils; 48import com.android.phone.R; 49 50import java.lang.Override; 51import java.util.Arrays; 52import java.util.ArrayList; 53import java.util.Collections; 54import java.util.HashMap; 55import java.util.List; 56import java.util.Map; 57import java.util.Objects; 58import java.util.Set; 59import java.util.concurrent.ConcurrentHashMap; 60 61/** 62 * Base class for CDMA and GSM connections. 63 */ 64abstract class TelephonyConnection extends Connection { 65 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 66 private static final int MSG_RINGBACK_TONE = 2; 67 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 68 private static final int MSG_DISCONNECT = 4; 69 private static final int MSG_MULTIPARTY_STATE_CHANGED = 5; 70 private static final int MSG_CONFERENCE_MERGE_FAILED = 6; 71 private static final int MSG_SUPP_SERVICE_NOTIFY = 7; 72 73 /** 74 * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their 75 * equivalents defined in {@link android.telecom.Connection}. 76 */ 77 private static final Map<String, String> sExtrasMap = createExtrasMap(); 78 79 private static final int MSG_SET_VIDEO_STATE = 8; 80 private static final int MSG_SET_VIDEO_PROVIDER = 9; 81 private static final int MSG_SET_AUDIO_QUALITY = 10; 82 private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11; 83 private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12; 84 private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13; 85 private static final int MSG_ON_HOLD_TONE = 14; 86 private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15; 87 private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16; 88 89 private final Handler mHandler = new Handler() { 90 @Override 91 public void handleMessage(Message msg) { 92 switch (msg.what) { 93 case MSG_PRECISE_CALL_STATE_CHANGED: 94 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 95 updateState(); 96 break; 97 case MSG_HANDOVER_STATE_CHANGED: 98 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 99 AsyncResult ar = (AsyncResult) msg.obj; 100 com.android.internal.telephony.Connection connection = 101 (com.android.internal.telephony.Connection) ar.result; 102 if (mOriginalConnection != null) { 103 if (connection != null && 104 ((connection.getAddress() != null && 105 mOriginalConnection.getAddress() != null && 106 mOriginalConnection.getAddress().contains(connection.getAddress())) || 107 connection.getState() == mOriginalConnection.getStateBeforeHandover())) { 108 Log.d(TelephonyConnection.this, 109 "SettingOriginalConnection " + mOriginalConnection.toString() 110 + " with " + connection.toString()); 111 setOriginalConnection(connection); 112 mWasImsConnection = false; 113 } 114 } else { 115 Log.w(TelephonyConnection.this, 116 "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)"); 117 } 118 break; 119 case MSG_RINGBACK_TONE: 120 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 121 // TODO: This code assumes that there is only one connection in the foreground 122 // call, in other words, it punts on network-mediated conference calling. 123 if (getOriginalConnection() != getForegroundConnection()) { 124 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 125 "not foreground connection, skipping"); 126 return; 127 } 128 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 129 break; 130 case MSG_DISCONNECT: 131 updateState(); 132 break; 133 case MSG_MULTIPARTY_STATE_CHANGED: 134 boolean isMultiParty = (Boolean) msg.obj; 135 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 136 mIsMultiParty = isMultiParty; 137 if (isMultiParty) { 138 notifyConferenceStarted(); 139 } 140 break; 141 case MSG_CONFERENCE_MERGE_FAILED: 142 notifyConferenceMergeFailed(); 143 break; 144 case MSG_SUPP_SERVICE_NOTIFY: 145 Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : " 146 +getPhone().getPhoneId()); 147 SuppServiceNotification mSsNotification = null; 148 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) { 149 mSsNotification = 150 (SuppServiceNotification)((AsyncResult) msg.obj).result; 151 if (mOriginalConnection != null && mSsNotification.history != null) { 152 Bundle lastForwardedNumber = new Bundle(); 153 Log.v(TelephonyConnection.this, 154 "Updating call history info in extras."); 155 lastForwardedNumber.putStringArrayList( 156 Connection.EXTRA_LAST_FORWARDED_NUMBER, 157 new ArrayList(Arrays.asList(mSsNotification.history))); 158 putExtras(lastForwardedNumber); 159 } 160 } 161 break; 162 163 case MSG_SET_VIDEO_STATE: 164 int videoState = (int) msg.obj; 165 setVideoState(videoState); 166 167 // A change to the video state of the call can influence whether or not it 168 // can be part of a conference. 169 refreshConferenceSupported(); 170 break; 171 172 case MSG_SET_VIDEO_PROVIDER: 173 VideoProvider videoProvider = (VideoProvider) msg.obj; 174 setVideoProvider(videoProvider); 175 break; 176 177 case MSG_SET_AUDIO_QUALITY: 178 int audioQuality = (int) msg.obj; 179 setAudioQuality(audioQuality); 180 break; 181 182 case MSG_SET_CONFERENCE_PARTICIPANTS: 183 List<ConferenceParticipant> participants = (List<ConferenceParticipant>) msg.obj; 184 updateConferenceParticipants(participants); 185 break; 186 187 case MSG_CONNECTION_EXTRAS_CHANGED: 188 final Bundle extras = (Bundle) msg.obj; 189 updateExtras(extras); 190 break; 191 192 case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES: 193 setOriginalConnectionCapabilities(msg.arg1); 194 break; 195 196 case MSG_ON_HOLD_TONE: 197 AsyncResult asyncResult = (AsyncResult) msg.obj; 198 Pair<com.android.internal.telephony.Connection, Boolean> heldInfo = 199 (Pair<com.android.internal.telephony.Connection, Boolean>) 200 asyncResult.result; 201 202 // Determines if the hold tone is starting or stopping. 203 boolean playTone = ((Boolean) (heldInfo.second)).booleanValue(); 204 205 // Determine which connection the hold tone is stopping or starting for 206 com.android.internal.telephony.Connection heldConnection = heldInfo.first; 207 208 // Only start or stop the hold tone if this is the connection which is starting 209 // or stopping the hold tone. 210 if (heldConnection == mOriginalConnection) { 211 // If starting the hold tone, send a connection event to Telecom which will 212 // cause it to play the on hold tone. 213 if (playTone) { 214 sendConnectionEvent(EVENT_ON_HOLD_TONE_START, null); 215 } else { 216 sendConnectionEvent(EVENT_ON_HOLD_TONE_END, null); 217 } 218 } 219 break; 220 221 case MSG_CDMA_VOICE_PRIVACY_ON: 222 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_ON received"); 223 setCdmaVoicePrivacy(true); 224 break; 225 case MSG_CDMA_VOICE_PRIVACY_OFF: 226 Log.d(this, "MSG_CDMA_VOICE_PRIVACY_OFF received"); 227 setCdmaVoicePrivacy(false); 228 break; 229 } 230 } 231 }; 232 233 /** 234 * A listener/callback mechanism that is specific communication from TelephonyConnections 235 * to TelephonyConnectionService (for now). It is more specific that Connection.Listener 236 * because it is only exposed in Telephony. 237 */ 238 public abstract static class TelephonyConnectionListener { 239 public void onOriginalConnectionConfigured(TelephonyConnection c) {} 240 } 241 242 private final PostDialListener mPostDialListener = new PostDialListener() { 243 @Override 244 public void onPostDialWait() { 245 Log.v(TelephonyConnection.this, "onPostDialWait"); 246 if (mOriginalConnection != null) { 247 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 248 } 249 } 250 251 @Override 252 public void onPostDialChar(char c) { 253 Log.v(TelephonyConnection.this, "onPostDialChar: %s", c); 254 if (mOriginalConnection != null) { 255 setNextPostDialChar(c); 256 } 257 } 258 }; 259 260 /** 261 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 262 */ 263 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 264 new com.android.internal.telephony.Connection.ListenerBase() { 265 @Override 266 public void onVideoStateChanged(int videoState) { 267 mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget(); 268 } 269 270 /* 271 * The {@link com.android.internal.telephony.Connection} has reported a change in 272 * connection capability. 273 * @param capabilities bit mask containing voice or video or both capabilities. 274 */ 275 @Override 276 public void onConnectionCapabilitiesChanged(int capabilities) { 277 mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES, 278 capabilities, 0).sendToTarget(); 279 } 280 281 /** 282 * The {@link com.android.internal.telephony.Connection} has reported a change in the 283 * video call provider. 284 * 285 * @param videoProvider The video call provider. 286 */ 287 @Override 288 public void onVideoProviderChanged(VideoProvider videoProvider) { 289 mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget(); 290 } 291 292 /** 293 * Used by {@link com.android.internal.telephony.Connection} to report a change in whether 294 * the call is being made over a wifi network. 295 * 296 * @param isWifi True if call is made over wifi. 297 */ 298 @Override 299 public void onWifiChanged(boolean isWifi) { 300 setWifi(isWifi); 301 } 302 303 /** 304 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 305 * audio quality for the current call. 306 * 307 * @param audioQuality The audio quality. 308 */ 309 @Override 310 public void onAudioQualityChanged(int audioQuality) { 311 mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget(); 312 } 313 /** 314 * Handles a change in the state of conference participant(s), as reported by the 315 * {@link com.android.internal.telephony.Connection}. 316 * 317 * @param participants The participant(s) which changed. 318 */ 319 @Override 320 public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) { 321 mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget(); 322 } 323 324 /* 325 * Handles a change to the multiparty state for this connection. 326 * 327 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 328 * otherwise. 329 */ 330 @Override 331 public void onMultipartyStateChanged(boolean isMultiParty) { 332 handleMultipartyStateChange(isMultiParty); 333 } 334 335 /** 336 * Handles the event that the request to merge calls failed. 337 */ 338 @Override 339 public void onConferenceMergedFailed() { 340 handleConferenceMergeFailed(); 341 } 342 343 @Override 344 public void onExtrasChanged(Bundle extras) { 345 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget(); 346 } 347 348 /** 349 * Handles the phone exiting ECM mode by updating the connection capabilities. During an 350 * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls. 351 */ 352 @Override 353 public void onExitedEcmMode() { 354 handleExitedEcmMode(); 355 } 356 }; 357 358 protected com.android.internal.telephony.Connection mOriginalConnection; 359 private Call.State mConnectionState = Call.State.IDLE; 360 private Bundle mOriginalConnectionExtras = new Bundle(); 361 private boolean mIsStateOverridden = false; 362 private Call.State mOriginalConnectionState = Call.State.IDLE; 363 private Call.State mConnectionOverriddenState = Call.State.IDLE; 364 365 private boolean mWasImsConnection; 366 367 /** 368 * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected. 369 */ 370 private boolean mIsMultiParty = false; 371 372 /** 373 * The {@link com.android.internal.telephony.Connection} capabilities associated with the 374 * current {@link #mOriginalConnection}. 375 */ 376 private int mOriginalConnectionCapabilities; 377 378 /** 379 * Determines if the {@link TelephonyConnection} is using wifi. 380 * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to 381 * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property. 382 */ 383 private boolean mIsWifi; 384 385 /** 386 * Determines the audio quality is high for the {@link TelephonyConnection}. 387 * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to 388 * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 389 */ 390 private boolean mHasHighDefAudio; 391 392 /** 393 * Indicates that the connection should be treated as an emergency call because the 394 * number dialed matches an internal list of emergency numbers. Does not guarantee whether 395 * the network will treat the call as an emergency call. 396 */ 397 private boolean mTreatAsEmergencyCall; 398 399 /** 400 * For video calls, indicates whether the outgoing video for the call can be paused using 401 * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 402 */ 403 private boolean mIsVideoPauseSupported; 404 405 /** 406 * Indicates whether this connection supports being a part of a conference.. 407 */ 408 private boolean mIsConferenceSupported; 409 410 /** 411 * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled. 412 */ 413 private boolean mIsCdmaVoicePrivacyEnabled; 414 415 /** 416 * Listeners to our TelephonyConnection specific callbacks 417 */ 418 private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap( 419 new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1)); 420 421 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection, 422 String callId) { 423 setTelecomCallId(callId); 424 if (originalConnection != null) { 425 setOriginalConnection(originalConnection); 426 } 427 } 428 429 /** 430 * Creates a clone of the current {@link TelephonyConnection}. 431 * 432 * @return The clone. 433 */ 434 public abstract TelephonyConnection cloneConnection(); 435 436 @Override 437 public void onCallAudioStateChanged(CallAudioState audioState) { 438 // TODO: update TTY mode. 439 if (getPhone() != null) { 440 getPhone().setEchoSuppressionEnabled(); 441 } 442 } 443 444 @Override 445 public void onStateChanged(int state) { 446 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 447 updateStatusHints(); 448 } 449 450 @Override 451 public void onDisconnect() { 452 Log.v(this, "onDisconnect"); 453 hangup(android.telephony.DisconnectCause.LOCAL); 454 } 455 456 /** 457 * Notifies this Connection of a request to disconnect a participant of the conference managed 458 * by the connection. 459 * 460 * @param endpoint the {@link Uri} of the participant to disconnect. 461 */ 462 @Override 463 public void onDisconnectConferenceParticipant(Uri endpoint) { 464 Log.v(this, "onDisconnectConferenceParticipant %s", endpoint); 465 466 if (mOriginalConnection == null) { 467 return; 468 } 469 470 mOriginalConnection.onDisconnectConferenceParticipant(endpoint); 471 } 472 473 @Override 474 public void onSeparate() { 475 Log.v(this, "onSeparate"); 476 if (mOriginalConnection != null) { 477 try { 478 mOriginalConnection.separate(); 479 } catch (CallStateException e) { 480 Log.e(this, e, "Call to Connection.separate failed with exception"); 481 } 482 } 483 } 484 485 @Override 486 public void onAbort() { 487 Log.v(this, "onAbort"); 488 hangup(android.telephony.DisconnectCause.LOCAL); 489 } 490 491 @Override 492 public void onHold() { 493 performHold(); 494 } 495 496 @Override 497 public void onUnhold() { 498 performUnhold(); 499 } 500 501 @Override 502 public void onAnswer(int videoState) { 503 Log.v(this, "onAnswer"); 504 if (isValidRingingCall() && getPhone() != null) { 505 try { 506 getPhone().acceptCall(videoState); 507 } catch (CallStateException e) { 508 Log.e(this, e, "Failed to accept call."); 509 } 510 } 511 } 512 513 @Override 514 public void onReject() { 515 Log.v(this, "onReject"); 516 if (isValidRingingCall()) { 517 hangup(android.telephony.DisconnectCause.INCOMING_REJECTED); 518 } 519 super.onReject(); 520 } 521 522 @Override 523 public void onPostDialContinue(boolean proceed) { 524 Log.v(this, "onPostDialContinue, proceed: " + proceed); 525 if (mOriginalConnection != null) { 526 if (proceed) { 527 mOriginalConnection.proceedAfterWaitChar(); 528 } else { 529 mOriginalConnection.cancelPostDial(); 530 } 531 } 532 } 533 534 /** 535 * Handles requests to pull an external call. 536 */ 537 @Override 538 public void onPullExternalCall() { 539 if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) != 540 Connection.PROPERTY_IS_EXTERNAL_CALL) { 541 Log.w(this, "onPullExternalCall - cannot pull non-external call"); 542 return; 543 } 544 545 if (mOriginalConnection != null) { 546 mOriginalConnection.pullExternalCall(); 547 } 548 } 549 550 public void performHold() { 551 Log.v(this, "performHold"); 552 // TODO: Can dialing calls be put on hold as well since they take up the 553 // foreground call slot? 554 if (Call.State.ACTIVE == mConnectionState) { 555 Log.v(this, "Holding active call"); 556 try { 557 Phone phone = mOriginalConnection.getCall().getPhone(); 558 Call ringingCall = phone.getRingingCall(); 559 560 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 561 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 562 // a call on hold while a call-waiting call exists, it'll end up accepting the 563 // call-waiting call, which is bad if that was not the user's intention. We are 564 // cheating here and simply skipping it because we know any attempt to hold a call 565 // while a call-waiting call is happening is likely a request from Telecom prior to 566 // accepting the call-waiting call. 567 // TODO: Investigate a better solution. It would be great here if we 568 // could "fake" hold by silencing the audio and microphone streams for this call 569 // instead of actually putting it on hold. 570 if (ringingCall.getState() != Call.State.WAITING) { 571 phone.switchHoldingAndActive(); 572 } 573 574 // TODO: Cdma calls are slightly different. 575 } catch (CallStateException e) { 576 Log.e(this, e, "Exception occurred while trying to put call on hold."); 577 } 578 } else { 579 Log.w(this, "Cannot put a call that is not currently active on hold."); 580 } 581 } 582 583 public void performUnhold() { 584 Log.v(this, "performUnhold"); 585 if (Call.State.HOLDING == mConnectionState) { 586 try { 587 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 588 // more than one call, one of them must always be active. In other words, if you 589 // have an active call and holding call, and you put the active call on hold, it 590 // will automatically activate the holding call. This is weird with how Telecom 591 // sends its commands. When a user opts to "unhold" a background call, telecom 592 // issues hold commands to all active calls, and then the unhold command to the 593 // background call. This means that we get two commands...each of which reduces to 594 // switchHoldingAndActive(). The result is that they simply cancel each other out. 595 // To fix this so that it works well with telecom we add a minor hack. If we 596 // have one telephony call, everything works as normally expected. But if we have 597 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 598 // requests already do what we want. If you've read up to this point, I'm very sorry 599 // that we are doing this. I didn't think of a better solution that wouldn't also 600 // make the Telecom APIs very ugly. 601 602 if (!hasMultipleTopLevelCalls()) { 603 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 604 } else { 605 Log.i(this, "Skipping unhold command for %s", this); 606 } 607 } catch (CallStateException e) { 608 Log.e(this, e, "Exception occurred while trying to release call from hold."); 609 } 610 } else { 611 Log.w(this, "Cannot release a call that is not already on hold from hold."); 612 } 613 } 614 615 public void performConference(TelephonyConnection otherConnection) { 616 Log.d(this, "performConference - %s", this); 617 if (getPhone() != null) { 618 try { 619 // We dont use the "other" connection because there is no concept of that in the 620 // implementation of calls inside telephony. Basically, you can "conference" and it 621 // will conference with the background call. We know that otherConnection is the 622 // background call because it would never have called setConferenceableConnections() 623 // otherwise. 624 getPhone().conference(); 625 } catch (CallStateException e) { 626 Log.e(this, e, "Failed to conference call."); 627 } 628 } 629 } 630 631 /** 632 * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based 633 * capabilities. 634 */ 635 protected int buildConnectionCapabilities() { 636 int callCapabilities = 0; 637 if (mOriginalConnection != null && mOriginalConnection.isIncoming()) { 638 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO; 639 } 640 if (isImsConnection()) { 641 if (!shouldTreatAsEmergencyCall()) { 642 callCapabilities |= CAPABILITY_SUPPORT_HOLD; 643 if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) { 644 callCapabilities |= CAPABILITY_HOLD; 645 } 646 } 647 } 648 649 return callCapabilities; 650 } 651 652 protected final void updateConnectionCapabilities() { 653 int newCapabilities = buildConnectionCapabilities(); 654 655 newCapabilities = applyOriginalConnectionCapabilities(newCapabilities); 656 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO, 657 mIsVideoPauseSupported && isVideoCapable()); 658 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL, 659 isExternalConnection() && isPullable()); 660 newCapabilities = applyConferenceTerminationCapabilities(newCapabilities); 661 662 if (getConnectionCapabilities() != newCapabilities) { 663 setConnectionCapabilities(newCapabilities); 664 } 665 } 666 667 protected int buildConnectionProperties() { 668 int connectionProperties = 0; 669 670 // If the phone is in ECM mode, mark the call to indicate that the callback number should be 671 // shown. 672 Phone phone = getPhone(); 673 if (phone != null && phone.isInEcm()) { 674 connectionProperties |= PROPERTY_SHOW_CALLBACK_NUMBER; 675 } 676 677 return connectionProperties; 678 } 679 680 /** 681 * Updates the properties of the connection. 682 */ 683 protected final void updateConnectionProperties() { 684 int newProperties = buildConnectionProperties(); 685 686 newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, mHasHighDefAudio); 687 newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi); 688 newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL, 689 isExternalConnection()); 690 newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY, 691 mIsCdmaVoicePrivacyEnabled); 692 693 if (getConnectionProperties() != newProperties) { 694 setConnectionProperties(newProperties); 695 } 696 } 697 698 protected final void updateAddress() { 699 updateConnectionCapabilities(); 700 updateConnectionProperties(); 701 if (mOriginalConnection != null) { 702 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 703 int presentation = mOriginalConnection.getNumberPresentation(); 704 if (!Objects.equals(address, getAddress()) || 705 presentation != getAddressPresentation()) { 706 Log.v(this, "updateAddress, address changed"); 707 setAddress(address, presentation); 708 } 709 710 String name = mOriginalConnection.getCnapName(); 711 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 712 if (!Objects.equals(name, getCallerDisplayName()) || 713 namePresentation != getCallerDisplayNamePresentation()) { 714 Log.v(this, "updateAddress, caller display name changed"); 715 setCallerDisplayName(name, namePresentation); 716 } 717 718 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 719 mTreatAsEmergencyCall = true; 720 } 721 722 // Changing the address of the connection can change whether it is an emergency call or 723 // not, which can impact whether it can be part of a conference. 724 refreshConferenceSupported(); 725 } 726 } 727 728 void onRemovedFromCallService() { 729 // Subclass can override this to do cleanup. 730 } 731 732 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 733 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 734 clearOriginalConnection(); 735 mOriginalConnectionExtras.clear(); 736 mOriginalConnection = originalConnection; 737 mOriginalConnection.setTelecomCallId(getTelecomCallId()); 738 getPhone().registerForPreciseCallStateChanged( 739 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 740 getPhone().registerForHandoverStateChanged( 741 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 742 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 743 getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null); 744 getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null); 745 getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null); 746 getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null); 747 getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null); 748 mOriginalConnection.addPostDialListener(mPostDialListener); 749 mOriginalConnection.addListener(mOriginalConnectionListener); 750 751 // Set video state and capabilities 752 setVideoState(mOriginalConnection.getVideoState()); 753 setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities()); 754 setWifi(mOriginalConnection.isWifi()); 755 setVideoProvider(mOriginalConnection.getVideoProvider()); 756 setAudioQuality(mOriginalConnection.getAudioQuality()); 757 setTechnologyTypeExtra(); 758 759 // Post update of extras to the handler; extras are updated via the handler to ensure thread 760 // safety. The Extras Bundle is cloned in case the original extras are modified while they 761 // are being added to mOriginalConnectionExtras in updateExtras. 762 Bundle connExtras = mOriginalConnection.getConnectionExtras(); 763 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null : 764 new Bundle(connExtras)).sendToTarget(); 765 766 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 767 mTreatAsEmergencyCall = true; 768 } 769 770 if (isImsConnection()) { 771 mWasImsConnection = true; 772 } 773 mIsMultiParty = mOriginalConnection.isMultiparty(); 774 775 Bundle extrasToPut = new Bundle(); 776 List<String> extrasToRemove = new ArrayList<>(); 777 if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) { 778 extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 779 } else { 780 extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL); 781 } 782 783 if (!mOriginalConnection.shouldAllowAddCallDuringVideoCall()) { 784 extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL, true); 785 } else { 786 extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL); 787 } 788 putExtras(extrasToPut); 789 removeExtras(extrasToRemove); 790 791 // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this 792 // should be executed *after* the above setters have run. 793 updateState(); 794 if (mOriginalConnection == null) { 795 Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " + 796 originalConnection); 797 } 798 799 fireOnOriginalConnectionConfigured(); 800 } 801 802 /** 803 * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom. 804 */ 805 private void setTechnologyTypeExtra() { 806 if (getPhone() != null) { 807 putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 808 } 809 } 810 811 /** 812 * Whether the connection should be treated as an emergency. 813 * @return {@code true} if the connection should be treated as an emergency call based 814 * on the number dialed, {@code false} otherwise. 815 */ 816 protected boolean shouldTreatAsEmergencyCall() { 817 return mTreatAsEmergencyCall; 818 } 819 820 /** 821 * Un-sets the underlying radio connection. 822 */ 823 void clearOriginalConnection() { 824 if (mOriginalConnection != null) { 825 if (getPhone() != null) { 826 getPhone().unregisterForPreciseCallStateChanged(mHandler); 827 getPhone().unregisterForRingbackTone(mHandler); 828 getPhone().unregisterForHandoverStateChanged(mHandler); 829 getPhone().unregisterForDisconnect(mHandler); 830 getPhone().unregisterForSuppServiceNotification(mHandler); 831 getPhone().unregisterForOnHoldTone(mHandler); 832 getPhone().unregisterForInCallVoicePrivacyOn(mHandler); 833 getPhone().unregisterForInCallVoicePrivacyOff(mHandler); 834 } 835 mOriginalConnection.removePostDialListener(mPostDialListener); 836 mOriginalConnection.removeListener(mOriginalConnectionListener); 837 mOriginalConnection = null; 838 } 839 } 840 841 protected void hangup(int telephonyDisconnectCode) { 842 if (mOriginalConnection != null) { 843 try { 844 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 845 // connection.hangup(). Without this change, the party originating the call will not 846 // get sent to voicemail if the user opts to reject the call. 847 if (isValidRingingCall()) { 848 Call call = getCall(); 849 if (call != null) { 850 call.hangup(); 851 } else { 852 Log.w(this, "Attempting to hangup a connection without backing call."); 853 } 854 } else { 855 // We still prefer to call connection.hangup() for non-ringing calls in order 856 // to support hanging-up specific calls within a conference call. If we invoked 857 // call.hangup() while in a conference, we would end up hanging up the entire 858 // conference call instead of the specific connection. 859 mOriginalConnection.hangup(); 860 } 861 } catch (CallStateException e) { 862 Log.e(this, e, "Call to Connection.hangup failed with exception"); 863 } 864 } 865 } 866 867 com.android.internal.telephony.Connection getOriginalConnection() { 868 return mOriginalConnection; 869 } 870 871 protected Call getCall() { 872 if (mOriginalConnection != null) { 873 return mOriginalConnection.getCall(); 874 } 875 return null; 876 } 877 878 Phone getPhone() { 879 Call call = getCall(); 880 if (call != null) { 881 return call.getPhone(); 882 } 883 return null; 884 } 885 886 private boolean hasMultipleTopLevelCalls() { 887 int numCalls = 0; 888 Phone phone = getPhone(); 889 if (phone != null) { 890 if (!phone.getRingingCall().isIdle()) { 891 numCalls++; 892 } 893 if (!phone.getForegroundCall().isIdle()) { 894 numCalls++; 895 } 896 if (!phone.getBackgroundCall().isIdle()) { 897 numCalls++; 898 } 899 } 900 return numCalls > 1; 901 } 902 903 private com.android.internal.telephony.Connection getForegroundConnection() { 904 if (getPhone() != null) { 905 return getPhone().getForegroundCall().getEarliestConnection(); 906 } 907 return null; 908 } 909 910 /** 911 * Checks for and returns the list of conference participants 912 * associated with this connection. 913 */ 914 public List<ConferenceParticipant> getConferenceParticipants() { 915 if (mOriginalConnection == null) { 916 Log.v(this, "Null mOriginalConnection, cannot get conf participants."); 917 return null; 918 } 919 return mOriginalConnection.getConferenceParticipants(); 920 } 921 922 /** 923 * Checks to see the original connection corresponds to an active incoming call. Returns false 924 * if there is no such actual call, or if the associated call is not incoming (See 925 * {@link Call.State#isRinging}). 926 */ 927 private boolean isValidRingingCall() { 928 if (getPhone() == null) { 929 Log.v(this, "isValidRingingCall, phone is null"); 930 return false; 931 } 932 933 Call ringingCall = getPhone().getRingingCall(); 934 if (!ringingCall.getState().isRinging()) { 935 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 936 return false; 937 } 938 939 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 940 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 941 return false; 942 } 943 944 Log.v(this, "isValidRingingCall, returning true"); 945 return true; 946 } 947 948 // Make sure the extras being passed into this method is a COPY of the original extras Bundle. 949 // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll 950 // below. 951 protected void updateExtras(Bundle extras) { 952 if (mOriginalConnection != null) { 953 if (extras != null) { 954 // Check if extras have changed and need updating. 955 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) { 956 if (Log.DEBUG) { 957 Log.d(TelephonyConnection.this, "Updating extras:"); 958 for (String key : extras.keySet()) { 959 Object value = extras.get(key); 960 if (value instanceof String) { 961 Log.d(this, "updateExtras Key=" + Log.pii(key) + 962 " value=" + Log.pii((String)value)); 963 } 964 } 965 } 966 mOriginalConnectionExtras.clear(); 967 968 mOriginalConnectionExtras.putAll(extras); 969 970 // Remap any string extras that have a remapping defined. 971 for (String key : mOriginalConnectionExtras.keySet()) { 972 if (sExtrasMap.containsKey(key)) { 973 String newKey = sExtrasMap.get(key); 974 mOriginalConnectionExtras.putString(newKey, extras.getString(key)); 975 mOriginalConnectionExtras.remove(key); 976 } 977 } 978 979 // Ensure extras are propagated to Telecom. 980 putExtras(mOriginalConnectionExtras); 981 } else { 982 Log.d(this, "Extras update not required"); 983 } 984 } else { 985 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 986 } 987 } 988 } 989 990 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 991 if (extras == null || newExtras == null) { 992 return extras == newExtras; 993 } 994 995 if (extras.size() != newExtras.size()) { 996 return false; 997 } 998 999 for(String key : extras.keySet()) { 1000 if (key != null) { 1001 final Object value = extras.get(key); 1002 final Object newValue = newExtras.get(key); 1003 if (!Objects.equals(value, newValue)) { 1004 return false; 1005 } 1006 } 1007 } 1008 return true; 1009 } 1010 1011 void setStateOverride(Call.State state) { 1012 mIsStateOverridden = true; 1013 mConnectionOverriddenState = state; 1014 // Need to keep track of the original connection's state before override. 1015 mOriginalConnectionState = mOriginalConnection.getState(); 1016 updateStateInternal(); 1017 } 1018 1019 void resetStateOverride() { 1020 mIsStateOverridden = false; 1021 updateStateInternal(); 1022 } 1023 1024 void updateStateInternal() { 1025 if (mOriginalConnection == null) { 1026 return; 1027 } 1028 Call.State newState; 1029 // If the state is overridden and the state of the original connection hasn't changed since, 1030 // then we continue in the overridden state, else we go to the original connection's state. 1031 if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { 1032 newState = mConnectionOverriddenState; 1033 } else { 1034 newState = mOriginalConnection.getState(); 1035 } 1036 Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, this); 1037 1038 if (mConnectionState != newState) { 1039 mConnectionState = newState; 1040 switch (newState) { 1041 case IDLE: 1042 break; 1043 case ACTIVE: 1044 setActiveInternal(); 1045 break; 1046 case HOLDING: 1047 setOnHold(); 1048 break; 1049 case DIALING: 1050 case ALERTING: 1051 setDialing(); 1052 break; 1053 case INCOMING: 1054 case WAITING: 1055 setRinging(); 1056 break; 1057 case DISCONNECTED: 1058 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1059 mOriginalConnection.getDisconnectCause(), 1060 mOriginalConnection.getVendorDisconnectCause())); 1061 close(); 1062 break; 1063 case DISCONNECTING: 1064 break; 1065 } 1066 } 1067 } 1068 1069 void updateState() { 1070 if (mOriginalConnection == null) { 1071 return; 1072 } 1073 1074 updateStateInternal(); 1075 updateStatusHints(); 1076 updateConnectionCapabilities(); 1077 updateConnectionProperties(); 1078 updateAddress(); 1079 updateMultiparty(); 1080 } 1081 1082 /** 1083 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 1084 */ 1085 private void updateMultiparty() { 1086 if (mOriginalConnection == null) { 1087 return; 1088 } 1089 1090 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 1091 mIsMultiParty = mOriginalConnection.isMultiparty(); 1092 1093 if (mIsMultiParty) { 1094 notifyConferenceStarted(); 1095 } 1096 } 1097 } 1098 1099 /** 1100 * Handles a failure when merging calls into a conference. 1101 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 1102 * listener. 1103 */ 1104 private void handleConferenceMergeFailed(){ 1105 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 1106 } 1107 1108 /** 1109 * Handles requests to update the multiparty state received via the 1110 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 1111 * listener. 1112 * <p> 1113 * Note: We post this to the mHandler to ensure that if a conference must be created as a 1114 * result of the multiparty state change, the conference creation happens on the correct 1115 * thread. This ensures that the thread check in 1116 * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)} 1117 * does not fire. 1118 * 1119 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 1120 */ 1121 private void handleMultipartyStateChange(boolean isMultiParty) { 1122 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 1123 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 1124 } 1125 1126 private void setActiveInternal() { 1127 if (getState() == STATE_ACTIVE) { 1128 Log.w(this, "Should not be called if this is already ACTIVE"); 1129 return; 1130 } 1131 1132 // When we set a call to active, we need to make sure that there are no other active 1133 // calls. However, the ordering of state updates to connections can be non-deterministic 1134 // since all connections register for state changes on the phone independently. 1135 // To "optimize", we check here to see if there already exists any active calls. If so, 1136 // we issue an update for those calls first to make sure we only have one top-level 1137 // active call. 1138 if (getConnectionService() != null) { 1139 for (Connection current : getConnectionService().getAllConnections()) { 1140 if (current != this && current instanceof TelephonyConnection) { 1141 TelephonyConnection other = (TelephonyConnection) current; 1142 if (other.getState() == STATE_ACTIVE) { 1143 other.updateState(); 1144 } 1145 } 1146 } 1147 } 1148 setActive(); 1149 } 1150 1151 private void close() { 1152 Log.v(this, "close"); 1153 clearOriginalConnection(); 1154 destroy(); 1155 } 1156 1157 /** 1158 * Determines if the current connection is video capable. 1159 * 1160 * A connection is deemed to be video capable if the original connection capabilities state that 1161 * both local and remote video is supported. 1162 * 1163 * @return {@code true} if the connection is video capable, {@code false} otherwise. 1164 */ 1165 private boolean isVideoCapable() { 1166 return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 1167 && can(mOriginalConnectionCapabilities, 1168 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 1169 } 1170 1171 /** 1172 * Determines if the current connection is an external connection. 1173 * 1174 * A connection is deemed to be external if the original connection capabilities state that it 1175 * is. 1176 * 1177 * @return {@code true} if the connection is external, {@code false} otherwise. 1178 */ 1179 private boolean isExternalConnection() { 1180 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1181 && can(mOriginalConnectionCapabilities, 1182 Capability.IS_EXTERNAL_CONNECTION); 1183 } 1184 1185 /** 1186 * Determines if the current connection is pullable. 1187 * 1188 * A connection is deemed to be pullable if the original connection capabilities state that it 1189 * is. 1190 * 1191 * @return {@code true} if the connection is pullable, {@code false} otherwise. 1192 */ 1193 private boolean isPullable() { 1194 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1195 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE); 1196 } 1197 1198 /** 1199 * Sets whether or not CDMA enhanced call privacy is enabled for this connection. 1200 */ 1201 private void setCdmaVoicePrivacy(boolean isEnabled) { 1202 if(mIsCdmaVoicePrivacyEnabled != isEnabled) { 1203 mIsCdmaVoicePrivacyEnabled = isEnabled; 1204 updateConnectionProperties(); 1205 } 1206 } 1207 1208 /** 1209 * Applies capabilities specific to conferences termination to the 1210 * {@code ConnectionCapabilities} bit-mask. 1211 * 1212 * @param capabilities The {@code ConnectionCapabilities} bit-mask. 1213 * @return The capabilities with the IMS conference capabilities applied. 1214 */ 1215 private int applyConferenceTerminationCapabilities(int capabilities) { 1216 int currentCapabilities = capabilities; 1217 1218 // An IMS call cannot be individually disconnected or separated from its parent conference. 1219 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 1220 if (!mWasImsConnection) { 1221 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 1222 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 1223 } 1224 1225 return currentCapabilities; 1226 } 1227 1228 /** 1229 * Stores the new original connection capabilities, and applies them to the current connection, 1230 * notifying any listeners as necessary. 1231 * 1232 * @param connectionCapabilities The original connection capabilties. 1233 */ 1234 public void setOriginalConnectionCapabilities(int connectionCapabilities) { 1235 mOriginalConnectionCapabilities = connectionCapabilities; 1236 updateConnectionCapabilities(); 1237 updateConnectionProperties(); 1238 } 1239 1240 /** 1241 * Called to apply the capabilities present in the {@link #mOriginalConnection} to this 1242 * {@link Connection}. Provides a mapping between the capabilities present in the original 1243 * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in 1244 * this {@link Connection}. 1245 * 1246 * @param capabilities The capabilities bitmask from the {@link Connection}. 1247 * @return the capabilities bitmask with the original connection capabilities remapped and 1248 * applied. 1249 */ 1250 public int applyOriginalConnectionCapabilities(int capabilities) { 1251 // We only support downgrading to audio if both the remote and local side support 1252 // downgrading to audio. 1253 boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities, 1254 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 1255 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 1256 capabilities = changeBitmask(capabilities, 1257 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio); 1258 1259 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 1260 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 1261 1262 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 1263 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 1264 1265 return capabilities; 1266 } 1267 1268 /** 1269 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 1270 * the {@link Connection#PROPERTY_WIFI} property. 1271 */ 1272 public void setWifi(boolean isWifi) { 1273 mIsWifi = isWifi; 1274 updateConnectionProperties(); 1275 updateStatusHints(); 1276 } 1277 1278 /** 1279 * Whether the call is using wifi. 1280 */ 1281 boolean isWifi() { 1282 return mIsWifi; 1283 } 1284 1285 /** 1286 * Sets the current call audio quality. Used during rebuild of the properties 1287 * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 1288 * 1289 * @param audioQuality The audio quality. 1290 */ 1291 public void setAudioQuality(int audioQuality) { 1292 mHasHighDefAudio = audioQuality == 1293 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1294 updateConnectionProperties(); 1295 } 1296 1297 void resetStateForConference() { 1298 if (getState() == Connection.STATE_HOLDING) { 1299 resetStateOverride(); 1300 } 1301 } 1302 1303 boolean setHoldingForConference() { 1304 if (getState() == Connection.STATE_ACTIVE) { 1305 setStateOverride(Call.State.HOLDING); 1306 return true; 1307 } 1308 return false; 1309 } 1310 1311 /** 1312 * For video calls, sets whether this connection supports pausing the outgoing video for the 1313 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1314 * 1315 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1316 */ 1317 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1318 mIsVideoPauseSupported = isVideoPauseSupported; 1319 } 1320 1321 /** 1322 * Sets whether this connection supports conference calling. 1323 * @param isConferenceSupported {@code true} if conference calling is supported by this 1324 * connection, {@code false} otherwise. 1325 */ 1326 public void setConferenceSupported(boolean isConferenceSupported) { 1327 mIsConferenceSupported = isConferenceSupported; 1328 } 1329 1330 /** 1331 * @return {@code true} if this connection supports merging calls into a conference. 1332 */ 1333 public boolean isConferenceSupported() { 1334 return mIsConferenceSupported; 1335 } 1336 1337 /** 1338 * Whether the original connection is an IMS connection. 1339 * @return {@code True} if the original connection is an IMS connection, {@code false} 1340 * otherwise. 1341 */ 1342 protected boolean isImsConnection() { 1343 com.android.internal.telephony.Connection originalConnection = getOriginalConnection(); 1344 return originalConnection != null && 1345 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1346 } 1347 1348 /** 1349 * Whether the original connection was ever an IMS connection, either before or now. 1350 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1351 * otherwise. 1352 */ 1353 public boolean wasImsConnection() { 1354 return mWasImsConnection; 1355 } 1356 1357 private static Uri getAddressFromNumber(String number) { 1358 // Address can be null for blocked calls. 1359 if (number == null) { 1360 number = ""; 1361 } 1362 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1363 } 1364 1365 /** 1366 * Changes a capabilities bit-mask to add or remove a capability. 1367 * 1368 * @param bitmask The bit-mask. 1369 * @param bitfield The bit-field to change. 1370 * @param enabled Whether the bit-field should be set or removed. 1371 * @return The bit-mask with the bit-field changed. 1372 */ 1373 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 1374 if (enabled) { 1375 return bitmask | bitfield; 1376 } else { 1377 return bitmask & ~bitfield; 1378 } 1379 } 1380 1381 private void updateStatusHints() { 1382 boolean isIncoming = isValidRingingCall(); 1383 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1384 int labelId = isIncoming 1385 ? R.string.status_hint_label_incoming_wifi_call 1386 : R.string.status_hint_label_wifi_call; 1387 1388 Context context = getPhone().getContext(); 1389 setStatusHints(new StatusHints( 1390 context.getString(labelId), 1391 Icon.createWithResource( 1392 context.getResources(), 1393 R.drawable.ic_signal_wifi_4_bar_24dp), 1394 null /* extras */)); 1395 } else { 1396 setStatusHints(null); 1397 } 1398 } 1399 1400 /** 1401 * Register a listener for {@link TelephonyConnection} specific triggers. 1402 * @param l The instance of the listener to add 1403 * @return The connection being listened to 1404 */ 1405 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 1406 mTelephonyListeners.add(l); 1407 // If we already have an original connection, let's call back immediately. 1408 // This would be the case for incoming calls. 1409 if (mOriginalConnection != null) { 1410 fireOnOriginalConnectionConfigured(); 1411 } 1412 return this; 1413 } 1414 1415 /** 1416 * Remove a listener for {@link TelephonyConnection} specific triggers. 1417 * @param l The instance of the listener to remove 1418 * @return The connection being listened to 1419 */ 1420 public final TelephonyConnection removeTelephonyConnectionListener( 1421 TelephonyConnectionListener l) { 1422 if (l != null) { 1423 mTelephonyListeners.remove(l); 1424 } 1425 return this; 1426 } 1427 1428 /** 1429 * Fire a callback to the various listeners for when the original connection is 1430 * set in this {@link TelephonyConnection} 1431 */ 1432 private final void fireOnOriginalConnectionConfigured() { 1433 for (TelephonyConnectionListener l : mTelephonyListeners) { 1434 l.onOriginalConnectionConfigured(this); 1435 } 1436 } 1437 1438 /** 1439 * Handles exiting ECM mode. 1440 */ 1441 protected void handleExitedEcmMode() { 1442 updateConnectionProperties(); 1443 } 1444 1445 /** 1446 * Determines whether the connection supports conference calling. A connection supports 1447 * conference calling if it: 1448 * 1. Is not an emergency call. 1449 * 2. Carrier supports conference calls. 1450 * 3. If call is a video call, carrier supports video conference calls. 1451 * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls. 1452 */ 1453 private void refreshConferenceSupported() { 1454 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 1455 Phone phone = getPhone(); 1456 boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1457 boolean isVoWifiEnabled = false; 1458 if (isIms) { 1459 ImsPhone imsPhone = (ImsPhone) phone; 1460 isVoWifiEnabled = imsPhone.isWifiCallingEnabled(); 1461 } 1462 PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils 1463 .makePstnPhoneAccountHandle(phone.getDefaultPhone()) 1464 : PhoneUtils.makePstnPhoneAccountHandle(phone); 1465 TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry 1466 .getInstance(getPhone().getContext()); 1467 boolean isConferencingSupported = telecomAccountRegistry 1468 .isMergeCallSupported(phoneAccountHandle); 1469 boolean isVideoConferencingSupported = telecomAccountRegistry 1470 .isVideoConferencingSupported(phoneAccountHandle); 1471 boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry 1472 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle); 1473 1474 Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " + 1475 "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported, 1476 isVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, isWifi(), 1477 isVoWifiEnabled); 1478 boolean isConferenceSupported = true; 1479 if (mTreatAsEmergencyCall) { 1480 isConferenceSupported = false; 1481 Log.d(this, "refreshConferenceSupported = false; emergency call"); 1482 } else if (!isConferencingSupported) { 1483 isConferenceSupported = false; 1484 Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf."); 1485 } else if (isVideoCall && !isVideoConferencingSupported) { 1486 isConferenceSupported = false; 1487 Log.d(this, "refreshConferenceSupported = false; video conf not supported."); 1488 } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) { 1489 isConferenceSupported = false; 1490 Log.d(this, 1491 "refreshConferenceSupported = false; can't merge wifi calls when voWifi off."); 1492 } else { 1493 Log.d(this, "refreshConferenceSupported = true."); 1494 } 1495 1496 if (isConferenceSupported != isConferenceSupported()) { 1497 setConferenceSupported(isConferenceSupported); 1498 notifyConferenceSupportedChanged(isConferenceSupported); 1499 } 1500 } 1501 /** 1502 * Provides a mapping from extras keys which may be found in the 1503 * {@link com.android.internal.telephony.Connection} to their equivalents defined in 1504 * {@link android.telecom.Connection}. 1505 * 1506 * @return Map containing key mappings. 1507 */ 1508 private static Map<String, String> createExtrasMap() { 1509 Map<String, String> result = new HashMap<String, String>(); 1510 result.put(ImsCallProfile.EXTRA_CHILD_NUMBER, 1511 android.telecom.Connection.EXTRA_CHILD_ADDRESS); 1512 result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT, 1513 android.telecom.Connection.EXTRA_CALL_SUBJECT); 1514 return Collections.unmodifiableMap(result); 1515 } 1516 1517 /** 1518 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 1519 * use in log statements. 1520 * 1521 * @return String representation of the connection. 1522 */ 1523 @Override 1524 public String toString() { 1525 StringBuilder sb = new StringBuilder(); 1526 sb.append("[TelephonyConnection objId:"); 1527 sb.append(System.identityHashCode(this)); 1528 sb.append(" telecomCallID:"); 1529 sb.append(getTelecomCallId()); 1530 sb.append(" type:"); 1531 if (isImsConnection()) { 1532 sb.append("ims"); 1533 } else if (this instanceof com.android.services.telephony.GsmConnection) { 1534 sb.append("gsm"); 1535 } else if (this instanceof CdmaConnection) { 1536 sb.append("cdma"); 1537 } 1538 sb.append(" state:"); 1539 sb.append(Connection.stateToString(getState())); 1540 sb.append(" capabilities:"); 1541 sb.append(capabilitiesToString(getConnectionCapabilities())); 1542 sb.append(" properties:"); 1543 sb.append(propertiesToString(getConnectionProperties())); 1544 sb.append(" address:"); 1545 sb.append(Log.pii(getAddress())); 1546 sb.append(" originalConnection:"); 1547 sb.append(mOriginalConnection); 1548 sb.append(" partOfConf:"); 1549 if (getConference() == null) { 1550 sb.append("N"); 1551 } else { 1552 sb.append("Y"); 1553 } 1554 sb.append(" confSupported:"); 1555 sb.append(mIsConferenceSupported ? "Y" : "N"); 1556 sb.append("]"); 1557 return sb.toString(); 1558 } 1559} 1560