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