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.Looper; 26 import android.os.Message; 27 import android.os.PersistableBundle; 28 import android.telecom.CallAudioState; 29 import android.telecom.ConferenceParticipant; 30 import android.telecom.Connection; 31 import android.telecom.PhoneAccount; 32 import android.telecom.PhoneAccountHandle; 33 import android.telecom.StatusHints; 34 import android.telecom.TelecomManager; 35 import android.telecom.VideoProfile; 36 import android.telephony.CarrierConfigManager; 37 import android.telephony.DisconnectCause; 38 import android.telephony.PhoneNumberUtils; 39 import android.telephony.TelephonyManager; 40 import android.telephony.ims.ImsCallProfile; 41 import android.text.TextUtils; 42 import android.util.Pair; 43 44 import com.android.ims.ImsCall; 45 import com.android.internal.telephony.Call; 46 import com.android.internal.telephony.CallFailCause; 47 import com.android.internal.telephony.CallStateException; 48 import com.android.internal.telephony.Connection.Capability; 49 import com.android.internal.telephony.Connection.PostDialListener; 50 import com.android.internal.telephony.Phone; 51 import com.android.internal.telephony.PhoneConstants; 52 import com.android.internal.telephony.gsm.SuppServiceNotification; 53 import com.android.internal.telephony.imsphone.ImsPhone; 54 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker; 55 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 56 import com.android.phone.ImsUtil; 57 import com.android.phone.PhoneGlobals; 58 import com.android.phone.PhoneUtils; 59 import com.android.phone.R; 60 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Collections; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Objects; 68 import java.util.Set; 69 import java.util.concurrent.ConcurrentHashMap; 70 71 /** 72 * Base class for CDMA and GSM connections. 73 */ 74 abstract class TelephonyConnection extends Connection implements Holdable { 75 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 76 private static final int MSG_RINGBACK_TONE = 2; 77 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 78 private static final int MSG_DISCONNECT = 4; 79 private static final int MSG_MULTIPARTY_STATE_CHANGED = 5; 80 private static final int MSG_CONFERENCE_MERGE_FAILED = 6; 81 private static final int MSG_SUPP_SERVICE_NOTIFY = 7; 82 83 /** 84 * Mappings from {@link com.android.internal.telephony.Connection} extras keys to their 85 * equivalents defined in {@link android.telecom.Connection}. 86 */ 87 private static final Map<String, String> sExtrasMap = createExtrasMap(); 88 89 private static final int MSG_SET_VIDEO_STATE = 8; 90 private static final int MSG_SET_VIDEO_PROVIDER = 9; 91 private static final int MSG_SET_AUDIO_QUALITY = 10; 92 private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11; 93 private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12; 94 private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13; 95 private static final int MSG_ON_HOLD_TONE = 14; 96 private static final int MSG_CDMA_VOICE_PRIVACY_ON = 15; 97 private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16; 98 private static final int MSG_HANGUP = 17; 99 100 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 101 @Override 102 public void handleMessage(Message msg) { 103 switch (msg.what) { 104 case MSG_PRECISE_CALL_STATE_CHANGED: 105 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 106 updateState(); 107 break; 108 case MSG_HANDOVER_STATE_CHANGED: 109 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 110 AsyncResult ar = (AsyncResult) msg.obj; 111 com.android.internal.telephony.Connection connection = 112 (com.android.internal.telephony.Connection) ar.result; 113 if (mOriginalConnection != null) { 114 if (connection != null && 115 ((connection.getAddress() != null && 116 mOriginalConnection.getAddress() != null && 117 mOriginalConnection.getAddress().contains(connection.getAddress())) || 118 connection.getState() == mOriginalConnection.getStateBeforeHandover())) { 119 Log.d(TelephonyConnection.this, 120 "SettingOriginalConnection " + mOriginalConnection.toString() 121 + " with " + connection.toString()); 122 setOriginalConnection(connection); 123 mWasImsConnection = false; 124 } 125 } else { 126 Log.w(TelephonyConnection.this, 127 "MSG_HANDOVER_STATE_CHANGED: mOriginalConnection==null - invalid state (not cleaned up)"); 128 } 129 break; 130 case MSG_RINGBACK_TONE: 131 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 132 // TODO: This code assumes that there is only one connection in the foreground 133 // call, in other words, it punts on network-mediated conference calling. 134 if (getOriginalConnection() != getForegroundConnection()) { 135 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 136 "not foreground connection, skipping"); 137 return; 138 } 139 setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result); 140 break; 141 case MSG_DISCONNECT: 142 updateState(); 143 break; 144 case MSG_MULTIPARTY_STATE_CHANGED: 145 boolean isMultiParty = (Boolean) msg.obj; 146 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 147 mIsMultiParty = isMultiParty; 148 if (isMultiParty) { 149 notifyConferenceStarted(); 150 } 151 break; 152 case MSG_CONFERENCE_MERGE_FAILED: 153 notifyConferenceMergeFailed(); 154 break; 155 case MSG_SUPP_SERVICE_NOTIFY: 156 Phone phone = getPhone(); 157 Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : " 158 + (phone != null ? Integer.toString(phone.getPhoneId()) 159 : "null")); 160 SuppServiceNotification mSsNotification = null; 161 if (msg.obj != null && ((AsyncResult) msg.obj).result != null) { 162 mSsNotification = 163 (SuppServiceNotification)((AsyncResult) msg.obj).result; 164 if (mOriginalConnection != null) { 165 handleSuppServiceNotification(mSsNotification); 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 case MSG_HANGUP: 240 int cause = (int) msg.obj; 241 hangup(cause); 242 break; 243 } 244 } 245 }; 246 247 /** 248 * Handles {@link SuppServiceNotification}s pertinent to Telephony. 249 * @param ssn the notification. 250 */ 251 private void handleSuppServiceNotification(SuppServiceNotification ssn) { 252 Log.i(this, "handleSuppServiceNotification: type=%d, code=%d", ssn.notificationType, 253 ssn.code); 254 if (ssn.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1 255 && ssn.code == SuppServiceNotification.CODE_1_CALL_FORWARDED) { 256 sendConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null); 257 } 258 sendSuppServiceNotificationEvent(ssn.notificationType, ssn.code); 259 } 260 261 /** 262 * Sends a supplementary service notification connection event. 263 * This connection event includes the type and code, as well as a human readable message which 264 * is suitable for display to the user if the UI chooses to do so. 265 * @param type the {@link SuppServiceNotification#type}. 266 * @param code the {@link SuppServiceNotification#code}. 267 */ 268 private void sendSuppServiceNotificationEvent(int type, int code) { 269 Bundle extras = new Bundle(); 270 extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_TYPE, type); 271 extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_CODE, code); 272 extras.putCharSequence(TelephonyManager.EXTRA_NOTIFICATION_MESSAGE, 273 getSuppServiceMessage(type, code)); 274 sendConnectionEvent(TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION, extras); 275 } 276 277 /** 278 * Retrieves a human-readable message for a supplementary service notification. 279 * This message is suitable for display to the user. 280 * @param type the code group. 281 * @param code the code. 282 * @return A {@link CharSequence} containing the message, or {@code null} if none defined. 283 */ 284 private CharSequence getSuppServiceMessage(int type, int code) { 285 int messageId = -1; 286 if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1) { 287 switch (code) { 288 case SuppServiceNotification.CODE_1_CALL_DEFLECTED: 289 messageId = R.string.supp_service_notification_call_deflected; 290 break; 291 case SuppServiceNotification.CODE_1_CALL_FORWARDED: 292 messageId = R.string.supp_service_notification_call_forwarded; 293 break; 294 case SuppServiceNotification.CODE_1_CALL_IS_WAITING: 295 messageId = R.string.supp_service_notification_call_waiting; 296 break; 297 case SuppServiceNotification.CODE_1_CLIR_SUPPRESSION_REJECTED: 298 messageId = R.string.supp_service_clir_suppression_rejected; 299 break; 300 case SuppServiceNotification.CODE_1_CUG_CALL: 301 messageId = R.string.supp_service_closed_user_group_call; 302 break; 303 case SuppServiceNotification.CODE_1_INCOMING_CALLS_BARRED: 304 messageId = R.string.supp_service_incoming_calls_barred; 305 break; 306 case SuppServiceNotification.CODE_1_OUTGOING_CALLS_BARRED: 307 messageId = R.string.supp_service_outgoing_calls_barred; 308 break; 309 case SuppServiceNotification.CODE_1_SOME_CF_ACTIVE: 310 // Intentional fall through. 311 case SuppServiceNotification.CODE_1_UNCONDITIONAL_CF_ACTIVE: 312 messageId = R.string.supp_service_call_forwarding_active; 313 break; 314 } 315 } else if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_2) { 316 switch (code) { 317 case SuppServiceNotification.CODE_2_ADDITIONAL_CALL_FORWARDED: 318 messageId = R.string.supp_service_additional_call_forwarded; 319 break; 320 case SuppServiceNotification.CODE_2_CALL_CONNECTED_ECT: 321 messageId = R.string.supp_service_additional_ect_connected; 322 break; 323 case SuppServiceNotification.CODE_2_CALL_CONNECTING_ECT: 324 messageId = R.string.supp_service_additional_ect_connecting; 325 break; 326 case SuppServiceNotification.CODE_2_CALL_ON_HOLD: 327 messageId = R.string.supp_service_call_on_hold; 328 break; 329 case SuppServiceNotification.CODE_2_CALL_RETRIEVED: 330 messageId = R.string.supp_service_call_resumed; 331 break; 332 case SuppServiceNotification.CODE_2_CUG_CALL: 333 messageId = R.string.supp_service_closed_user_group_call; 334 break; 335 case SuppServiceNotification.CODE_2_DEFLECTED_CALL: 336 messageId = R.string.supp_service_deflected_call; 337 break; 338 case SuppServiceNotification.CODE_2_FORWARDED_CALL: 339 messageId = R.string.supp_service_forwarded_call; 340 break; 341 case SuppServiceNotification.CODE_2_MULTI_PARTY_CALL: 342 messageId = R.string.supp_service_conference_call; 343 break; 344 case SuppServiceNotification.CODE_2_ON_HOLD_CALL_RELEASED: 345 messageId = R.string.supp_service_held_call_released; 346 break; 347 } 348 } 349 if (messageId != -1 && getPhone() != null && getPhone().getContext() != null) { 350 return getPhone().getContext().getText(messageId); 351 } else { 352 return null; 353 } 354 } 355 356 /** 357 * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise. 358 */ 359 public boolean isCarrierVideoConferencingSupported() { 360 return mIsCarrierVideoConferencingSupported; 361 } 362 363 /** 364 * A listener/callback mechanism that is specific communication from TelephonyConnections 365 * to TelephonyConnectionService (for now). It is more specific that Connection.Listener 366 * because it is only exposed in Telephony. 367 */ 368 public abstract static class TelephonyConnectionListener { 369 public void onOriginalConnectionConfigured(TelephonyConnection c) {} 370 public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {} 371 } 372 373 private final PostDialListener mPostDialListener = new PostDialListener() { 374 @Override 375 public void onPostDialWait() { 376 Log.v(TelephonyConnection.this, "onPostDialWait"); 377 if (mOriginalConnection != null) { 378 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 379 } 380 } 381 382 @Override 383 public void onPostDialChar(char c) { 384 Log.v(TelephonyConnection.this, "onPostDialChar: %s", c); 385 if (mOriginalConnection != null) { 386 setNextPostDialChar(c); 387 } 388 } 389 }; 390 391 /** 392 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 393 */ 394 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 395 new com.android.internal.telephony.Connection.ListenerBase() { 396 @Override 397 public void onVideoStateChanged(int videoState) { 398 mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget(); 399 } 400 401 /* 402 * The {@link com.android.internal.telephony.Connection} has reported a change in 403 * connection capability. 404 * @param capabilities bit mask containing voice or video or both capabilities. 405 */ 406 @Override 407 public void onConnectionCapabilitiesChanged(int capabilities) { 408 mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES, 409 capabilities, 0).sendToTarget(); 410 } 411 412 /** 413 * The {@link com.android.internal.telephony.Connection} has reported a change in the 414 * video call provider. 415 * 416 * @param videoProvider The video call provider. 417 */ 418 @Override 419 public void onVideoProviderChanged(VideoProvider videoProvider) { 420 mHandler.obtainMessage(MSG_SET_VIDEO_PROVIDER, videoProvider).sendToTarget(); 421 } 422 423 /** 424 * Used by {@link com.android.internal.telephony.Connection} to report a change in whether 425 * the call is being made over a wifi network. 426 * 427 * @param isWifi True if call is made over wifi. 428 */ 429 @Override 430 public void onWifiChanged(boolean isWifi) { 431 setWifi(isWifi); 432 } 433 434 /** 435 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 436 * audio quality for the current call. 437 * 438 * @param audioQuality The audio quality. 439 */ 440 @Override 441 public void onAudioQualityChanged(int audioQuality) { 442 mHandler.obtainMessage(MSG_SET_AUDIO_QUALITY, audioQuality).sendToTarget(); 443 } 444 /** 445 * Handles a change in the state of conference participant(s), as reported by the 446 * {@link com.android.internal.telephony.Connection}. 447 * 448 * @param participants The participant(s) which changed. 449 */ 450 @Override 451 public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) { 452 mHandler.obtainMessage(MSG_SET_CONFERENCE_PARTICIPANTS, participants).sendToTarget(); 453 } 454 455 /* 456 * Handles a change to the multiparty state for this connection. 457 * 458 * @param isMultiParty {@code true} if the call became multiparty, {@code false} 459 * otherwise. 460 */ 461 @Override 462 public void onMultipartyStateChanged(boolean isMultiParty) { 463 handleMultipartyStateChange(isMultiParty); 464 } 465 466 /** 467 * Handles the event that the request to merge calls failed. 468 */ 469 @Override 470 public void onConferenceMergedFailed() { 471 handleConferenceMergeFailed(); 472 } 473 474 @Override 475 public void onExtrasChanged(Bundle extras) { 476 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, extras).sendToTarget(); 477 } 478 479 /** 480 * Handles the phone exiting ECM mode by updating the connection capabilities. During an 481 * ongoing call, if ECM mode is exited, we will re-enable mute for CDMA calls. 482 */ 483 @Override 484 public void onExitedEcmMode() { 485 handleExitedEcmMode(); 486 } 487 488 /** 489 * Called from {@link ImsPhoneCallTracker} when a request to pull an external call has 490 * failed. 491 * @param externalConnection 492 */ 493 @Override 494 public void onCallPullFailed(com.android.internal.telephony.Connection externalConnection) { 495 if (externalConnection == null) { 496 return; 497 } 498 499 Log.i(this, "onCallPullFailed - pull failed; swapping back to call: %s", 500 externalConnection); 501 502 // Inform the InCallService of the fact that the call pull failed (it may choose to 503 // display a message informing the user of the pull failure). 504 sendConnectionEvent(Connection.EVENT_CALL_PULL_FAILED, null); 505 506 // Swap the ImsPhoneConnection we used to do the pull for the ImsExternalConnection 507 // which originally represented the call. 508 setOriginalConnection(externalConnection); 509 510 // Set our state to active again since we're no longer pulling. 511 setActiveInternal(); 512 } 513 514 /** 515 * Called from {@link ImsPhoneCallTracker} when a handover to WIFI has failed. 516 */ 517 @Override 518 public void onHandoverToWifiFailed() { 519 sendConnectionEvent(TelephonyManager.EVENT_HANDOVER_TO_WIFI_FAILED, null); 520 } 521 522 /** 523 * Informs the {@link android.telecom.ConnectionService} of a connection event raised by the 524 * original connection. 525 * @param event The connection event. 526 * @param extras The extras. 527 */ 528 @Override 529 public void onConnectionEvent(String event, Bundle extras) { 530 sendConnectionEvent(event, extras); 531 } 532 533 @Override 534 public void onRttModifyRequestReceived() { 535 sendRemoteRttRequest(); 536 } 537 538 @Override 539 public void onRttModifyResponseReceived(int status) { 540 updateConnectionProperties(); 541 if (status == RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) { 542 sendRttInitiationSuccess(); 543 } else { 544 sendRttInitiationFailure(status); 545 } 546 } 547 548 @Override 549 public void onDisconnect(int cause) { 550 Log.i(this, "onDisconnect: callId=%s, cause=%s", getTelecomCallId(), 551 DisconnectCause.toString(cause)); 552 mHandler.obtainMessage(MSG_DISCONNECT).sendToTarget(); 553 } 554 555 @Override 556 public void onRttInitiated() { 557 updateConnectionProperties(); 558 sendRttInitiationSuccess(); 559 } 560 561 @Override 562 public void onRttTerminated() { 563 updateConnectionProperties(); 564 sendRttSessionRemotelyTerminated(); 565 } 566 }; 567 568 protected com.android.internal.telephony.Connection mOriginalConnection; 569 private Call.State mConnectionState = Call.State.IDLE; 570 private Bundle mOriginalConnectionExtras = new Bundle(); 571 private boolean mIsStateOverridden = false; 572 private Call.State mOriginalConnectionState = Call.State.IDLE; 573 private Call.State mConnectionOverriddenState = Call.State.IDLE; 574 private RttTextStream mRttTextStream = null; 575 576 private boolean mWasImsConnection; 577 578 /** 579 * Tracks the multiparty state of the ImsCall so that changes in the bit state can be detected. 580 */ 581 private boolean mIsMultiParty = false; 582 583 /** 584 * The {@link com.android.internal.telephony.Connection} capabilities associated with the 585 * current {@link #mOriginalConnection}. 586 */ 587 private int mOriginalConnectionCapabilities; 588 589 /** 590 * Determines if the {@link TelephonyConnection} is using wifi. 591 * This is used when {@link TelephonyConnection#updateConnectionProperties()} is called to 592 * indicate whether a call has the {@link Connection#PROPERTY_WIFI} property. 593 */ 594 private boolean mIsWifi; 595 596 /** 597 * Determines the audio quality is high for the {@link TelephonyConnection}. 598 * This is used when {@link TelephonyConnection#updateConnectionProperties}} is called to 599 * indicate whether a call has the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 600 */ 601 private boolean mHasHighDefAudio; 602 603 /** 604 * Indicates that the connection should be treated as an emergency call because the 605 * number dialed matches an internal list of emergency numbers. Does not guarantee whether 606 * the network will treat the call as an emergency call. 607 */ 608 private boolean mTreatAsEmergencyCall; 609 610 /** 611 * For video calls, indicates whether the outgoing video for the call can be paused using 612 * the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 613 */ 614 private boolean mIsVideoPauseSupported; 615 616 /** 617 * Indicates whether this connection supports being a part of a conference.. 618 */ 619 private boolean mIsConferenceSupported; 620 621 /** 622 * Indicates whether managing conference call is supported after this connection being 623 * a part of a IMS conference. 624 */ 625 private boolean mIsManageImsConferenceCallSupported; 626 627 /** 628 * Indicates whether the carrier supports video conferencing; captures the current state of the 629 * carrier config 630 * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}. 631 */ 632 private boolean mIsCarrierVideoConferencingSupported; 633 634 /** 635 * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled. 636 */ 637 private boolean mIsCdmaVoicePrivacyEnabled; 638 639 /** 640 * Indicates whether this call is an outgoing call. 641 */ 642 protected final boolean mIsOutgoing; 643 644 /** 645 * Indicates whether the connection can be held. This filed combined with the state of the 646 * connection can determine whether {@link Connection#CAPABILITY_HOLD} should be added to the 647 * connection. 648 */ 649 private boolean mIsHoldable; 650 651 /** 652 * Indicates whether this call is using assisted dialing. 653 */ 654 private boolean mIsUsingAssistedDialing; 655 656 /** 657 * Indicates whether this connection supports showing preciese call failed cause. 658 */ 659 private boolean mShowPreciseFailedCause; 660 661 /** 662 * Listeners to our TelephonyConnection specific callbacks 663 */ 664 private final Set<TelephonyConnectionListener> mTelephonyListeners = Collections.newSetFromMap( 665 new ConcurrentHashMap<TelephonyConnectionListener, Boolean>(8, 0.9f, 1)); 666 667 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection, 668 String callId, boolean isOutgoingCall) { 669 mIsOutgoing = isOutgoingCall; 670 setTelecomCallId(callId); 671 if (originalConnection != null) { 672 setOriginalConnection(originalConnection); 673 } 674 } 675 676 /** 677 * Creates a clone of the current {@link TelephonyConnection}. 678 * 679 * @return The clone. 680 */ 681 public abstract TelephonyConnection cloneConnection(); 682 683 @Override 684 public void onCallAudioStateChanged(CallAudioState audioState) { 685 // TODO: update TTY mode. 686 if (getPhone() != null) { 687 getPhone().setEchoSuppressionEnabled(); 688 } 689 } 690 691 @Override 692 public void onStateChanged(int state) { 693 Log.v(this, "onStateChanged, state: " + Connection.stateToString(state)); 694 updateStatusHints(); 695 } 696 697 @Override 698 public void onDisconnect() { 699 Log.v(this, "onDisconnect"); 700 mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget(); 701 } 702 703 /** 704 * Notifies this Connection of a request to disconnect a participant of the conference managed 705 * by the connection. 706 * 707 * @param endpoint the {@link Uri} of the participant to disconnect. 708 */ 709 @Override 710 public void onDisconnectConferenceParticipant(Uri endpoint) { 711 Log.v(this, "onDisconnectConferenceParticipant %s", endpoint); 712 713 if (mOriginalConnection == null) { 714 return; 715 } 716 717 mOriginalConnection.onDisconnectConferenceParticipant(endpoint); 718 } 719 720 @Override 721 public void onSeparate() { 722 Log.v(this, "onSeparate"); 723 if (mOriginalConnection != null) { 724 try { 725 mOriginalConnection.separate(); 726 } catch (CallStateException e) { 727 Log.e(this, e, "Call to Connection.separate failed with exception"); 728 } 729 } 730 } 731 732 @Override 733 public void onAbort() { 734 Log.v(this, "onAbort"); 735 mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.LOCAL).sendToTarget(); 736 } 737 738 @Override 739 public void onHold() { 740 performHold(); 741 } 742 743 @Override 744 public void onUnhold() { 745 performUnhold(); 746 } 747 748 @Override 749 public void onAnswer(int videoState) { 750 Log.v(this, "onAnswer"); 751 if (isValidRingingCall() && getPhone() != null) { 752 try { 753 getPhone().acceptCall(videoState); 754 } catch (CallStateException e) { 755 Log.e(this, e, "Failed to accept call."); 756 } 757 } 758 } 759 760 @Override 761 public void onDeflect(Uri address) { 762 Log.v(this, "onDeflect"); 763 if (mOriginalConnection != null && isValidRingingCall()) { 764 if (address == null) { 765 Log.w(this, "call deflect address uri is null"); 766 return; 767 } 768 String scheme = address.getScheme(); 769 String deflectNumber = ""; 770 String uriString = address.getSchemeSpecificPart(); 771 if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 772 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 773 Log.w(this, "onDeflect, address scheme is not of type tel instead: " + 774 scheme); 775 return; 776 } 777 if (PhoneNumberUtils.isUriNumber(uriString)) { 778 Log.w(this, "Invalid deflect address. Not a legal PSTN number."); 779 return; 780 } 781 deflectNumber = PhoneNumberUtils.convertAndStrip(uriString); 782 if (TextUtils.isEmpty(deflectNumber)) { 783 Log.w(this, "Empty deflect number obtained from address uri"); 784 return; 785 } 786 } else { 787 Log.w(this, "Cannot deflect to voicemail uri"); 788 return; 789 } 790 791 try { 792 mOriginalConnection.deflect(deflectNumber); 793 } catch (CallStateException e) { 794 Log.e(this, e, "Failed to deflect call."); 795 } 796 } 797 } 798 799 @Override 800 public void onReject() { 801 Log.v(this, "onReject"); 802 if (isValidRingingCall()) { 803 mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.INCOMING_REJECTED) 804 .sendToTarget(); 805 } 806 super.onReject(); 807 } 808 809 @Override 810 public void onPostDialContinue(boolean proceed) { 811 Log.v(this, "onPostDialContinue, proceed: " + proceed); 812 if (mOriginalConnection != null) { 813 if (proceed) { 814 mOriginalConnection.proceedAfterWaitChar(); 815 } else { 816 mOriginalConnection.cancelPostDial(); 817 } 818 } 819 } 820 821 /** 822 * Handles requests to pull an external call. 823 */ 824 @Override 825 public void onPullExternalCall() { 826 if ((getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) != 827 Connection.PROPERTY_IS_EXTERNAL_CALL) { 828 Log.w(this, "onPullExternalCall - cannot pull non-external call"); 829 return; 830 } 831 832 if (mOriginalConnection != null) { 833 mOriginalConnection.pullExternalCall(); 834 } 835 } 836 837 @Override 838 public void onStartRtt(RttTextStream textStream) { 839 if (isImsConnection()) { 840 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection; 841 if (originalConnection.isRttEnabledForCall()) { 842 originalConnection.setCurrentRttTextStream(textStream); 843 } else { 844 originalConnection.sendRttModifyRequest(textStream); 845 } 846 } else { 847 Log.w(this, "onStartRtt - not in IMS, so RTT cannot be enabled."); 848 } 849 } 850 851 @Override 852 public void onStopRtt() { 853 Log.i(this, "Stopping RTT currently not supported. Doing nothing."); 854 } 855 856 @Override 857 public void handleRttUpgradeResponse(RttTextStream textStream) { 858 if (!isImsConnection()) { 859 Log.w(this, "handleRttUpgradeResponse - not in IMS, so RTT cannot be enabled."); 860 return; 861 } 862 ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection; 863 originalConnection.sendRttModifyResponse(textStream); 864 } 865 866 public void performHold() { 867 Log.v(this, "performHold"); 868 // TODO: Can dialing calls be put on hold as well since they take up the 869 // foreground call slot? 870 if (Call.State.ACTIVE == mConnectionState) { 871 Log.v(this, "Holding active call"); 872 try { 873 Phone phone = mOriginalConnection.getCall().getPhone(); 874 Call ringingCall = phone.getRingingCall(); 875 876 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 877 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 878 // a call on hold while a call-waiting call exists, it'll end up accepting the 879 // call-waiting call, which is bad if that was not the user's intention. We are 880 // cheating here and simply skipping it because we know any attempt to hold a call 881 // while a call-waiting call is happening is likely a request from Telecom prior to 882 // accepting the call-waiting call. 883 // TODO: Investigate a better solution. It would be great here if we 884 // could "fake" hold by silencing the audio and microphone streams for this call 885 // instead of actually putting it on hold. 886 if (ringingCall.getState() != Call.State.WAITING) { 887 phone.switchHoldingAndActive(); 888 } 889 890 // TODO: Cdma calls are slightly different. 891 } catch (CallStateException e) { 892 Log.e(this, e, "Exception occurred while trying to put call on hold."); 893 } 894 } else { 895 Log.w(this, "Cannot put a call that is not currently active on hold."); 896 } 897 } 898 899 public void performUnhold() { 900 Log.v(this, "performUnhold"); 901 if (Call.State.HOLDING == mConnectionState) { 902 try { 903 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 904 // more than one call, one of them must always be active. In other words, if you 905 // have an active call and holding call, and you put the active call on hold, it 906 // will automatically activate the holding call. This is weird with how Telecom 907 // sends its commands. When a user opts to "unhold" a background call, telecom 908 // issues hold commands to all active calls, and then the unhold command to the 909 // background call. This means that we get two commands...each of which reduces to 910 // switchHoldingAndActive(). The result is that they simply cancel each other out. 911 // To fix this so that it works well with telecom we add a minor hack. If we 912 // have one telephony call, everything works as normally expected. But if we have 913 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 914 // requests already do what we want. If you've read up to this point, I'm very sorry 915 // that we are doing this. I didn't think of a better solution that wouldn't also 916 // make the Telecom APIs very ugly. 917 918 if (!hasMultipleTopLevelCalls()) { 919 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 920 } else { 921 Log.i(this, "Skipping unhold command for %s", this); 922 } 923 } catch (CallStateException e) { 924 Log.e(this, e, "Exception occurred while trying to release call from hold."); 925 } 926 } else { 927 Log.w(this, "Cannot release a call that is not already on hold from hold."); 928 } 929 } 930 931 public void performConference(Connection otherConnection) { 932 Log.d(this, "performConference - %s", this); 933 if (getPhone() != null) { 934 try { 935 // We dont use the "other" connection because there is no concept of that in the 936 // implementation of calls inside telephony. Basically, you can "conference" and it 937 // will conference with the background call. We know that otherConnection is the 938 // background call because it would never have called setConferenceableConnections() 939 // otherwise. 940 getPhone().conference(); 941 } catch (CallStateException e) { 942 Log.e(this, e, "Failed to conference call."); 943 } 944 } 945 } 946 947 /** 948 * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based 949 * capabilities. 950 */ 951 protected int buildConnectionCapabilities() { 952 int callCapabilities = 0; 953 if (mOriginalConnection != null && mOriginalConnection.isIncoming()) { 954 callCapabilities |= CAPABILITY_SPEED_UP_MT_AUDIO; 955 } 956 if (!shouldTreatAsEmergencyCall() && isImsConnection() && canHoldImsCalls()) { 957 callCapabilities |= CAPABILITY_SUPPORT_HOLD; 958 if (mIsHoldable && (getState() == STATE_ACTIVE || getState() == STATE_HOLDING)) { 959 callCapabilities |= CAPABILITY_HOLD; 960 } 961 } 962 963 Log.d(this, "buildConnectionCapabilities: isHoldable = " 964 + mIsHoldable + " State = " + getState() + " capabilities = " + callCapabilities); 965 966 return callCapabilities; 967 } 968 969 protected final void updateConnectionCapabilities() { 970 int newCapabilities = buildConnectionCapabilities(); 971 972 newCapabilities = applyOriginalConnectionCapabilities(newCapabilities); 973 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO, 974 mIsVideoPauseSupported && isVideoCapable()); 975 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_CAN_PULL_CALL, 976 isExternalConnection() && isPullable()); 977 newCapabilities = applyConferenceTerminationCapabilities(newCapabilities); 978 newCapabilities = changeBitmask(newCapabilities, CAPABILITY_SUPPORT_DEFLECT, 979 isImsConnection() && canDeflectImsCalls()); 980 981 if (getConnectionCapabilities() != newCapabilities) { 982 setConnectionCapabilities(newCapabilities); 983 } 984 } 985 986 protected int buildConnectionProperties() { 987 int connectionProperties = 0; 988 989 // If the phone is in ECM mode, mark the call to indicate that the callback number should be 990 // shown. 991 Phone phone = getPhone(); 992 if (phone != null && phone.isInEcm()) { 993 connectionProperties |= PROPERTY_EMERGENCY_CALLBACK_MODE; 994 } 995 996 return connectionProperties; 997 } 998 999 /** 1000 * Updates the properties of the connection. 1001 */ 1002 protected final void updateConnectionProperties() { 1003 int newProperties = buildConnectionProperties(); 1004 1005 newProperties = changeBitmask(newProperties, PROPERTY_HIGH_DEF_AUDIO, 1006 hasHighDefAudioProperty()); 1007 newProperties = changeBitmask(newProperties, PROPERTY_WIFI, mIsWifi); 1008 newProperties = changeBitmask(newProperties, PROPERTY_IS_EXTERNAL_CALL, 1009 isExternalConnection()); 1010 newProperties = changeBitmask(newProperties, PROPERTY_HAS_CDMA_VOICE_PRIVACY, 1011 mIsCdmaVoicePrivacyEnabled); 1012 newProperties = changeBitmask(newProperties, PROPERTY_ASSISTED_DIALING_USED, 1013 mIsUsingAssistedDialing); 1014 newProperties = changeBitmask(newProperties, PROPERTY_IS_RTT, isRtt()); 1015 1016 if (getConnectionProperties() != newProperties) { 1017 setConnectionProperties(newProperties); 1018 } 1019 } 1020 1021 protected final void updateAddress() { 1022 updateConnectionCapabilities(); 1023 updateConnectionProperties(); 1024 if (mOriginalConnection != null) { 1025 Uri address = getAddressFromNumber(mOriginalConnection.getAddress()); 1026 int presentation = mOriginalConnection.getNumberPresentation(); 1027 if (!Objects.equals(address, getAddress()) || 1028 presentation != getAddressPresentation()) { 1029 Log.v(this, "updateAddress, address changed"); 1030 if ((getConnectionProperties() & PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0) { 1031 address = null; 1032 } 1033 setAddress(address, presentation); 1034 } 1035 1036 String name = filterCnapName(mOriginalConnection.getCnapName()); 1037 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 1038 if (!Objects.equals(name, getCallerDisplayName()) || 1039 namePresentation != getCallerDisplayNamePresentation()) { 1040 Log.v(this, "updateAddress, caller display name changed"); 1041 setCallerDisplayName(name, namePresentation); 1042 } 1043 1044 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 1045 mTreatAsEmergencyCall = true; 1046 } 1047 1048 // Changing the address of the connection can change whether it is an emergency call or 1049 // not, which can impact whether it can be part of a conference. 1050 refreshConferenceSupported(); 1051 } 1052 } 1053 1054 void onRemovedFromCallService() { 1055 // Subclass can override this to do cleanup. 1056 } 1057 1058 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 1059 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 1060 clearOriginalConnection(); 1061 mOriginalConnectionExtras.clear(); 1062 mOriginalConnection = originalConnection; 1063 mOriginalConnection.setTelecomCallId(getTelecomCallId()); 1064 getPhone().registerForPreciseCallStateChanged( 1065 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 1066 getPhone().registerForHandoverStateChanged( 1067 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 1068 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 1069 getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null); 1070 getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null); 1071 getPhone().registerForInCallVoicePrivacyOn(mHandler, MSG_CDMA_VOICE_PRIVACY_ON, null); 1072 getPhone().registerForInCallVoicePrivacyOff(mHandler, MSG_CDMA_VOICE_PRIVACY_OFF, null); 1073 mOriginalConnection.addPostDialListener(mPostDialListener); 1074 mOriginalConnection.addListener(mOriginalConnectionListener); 1075 1076 // Set video state and capabilities 1077 setVideoState(mOriginalConnection.getVideoState()); 1078 setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities()); 1079 setWifi(mOriginalConnection.isWifi()); 1080 setAudioModeIsVoip(mOriginalConnection.getAudioModeIsVoip()); 1081 setVideoProvider(mOriginalConnection.getVideoProvider()); 1082 setAudioQuality(mOriginalConnection.getAudioQuality()); 1083 setTechnologyTypeExtra(); 1084 1085 // Post update of extras to the handler; extras are updated via the handler to ensure thread 1086 // safety. The Extras Bundle is cloned in case the original extras are modified while they 1087 // are being added to mOriginalConnectionExtras in updateExtras. 1088 Bundle connExtras = mOriginalConnection.getConnectionExtras(); 1089 mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null : 1090 new Bundle(connExtras)).sendToTarget(); 1091 1092 if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) { 1093 mTreatAsEmergencyCall = true; 1094 } 1095 1096 if (isImsConnection()) { 1097 mWasImsConnection = true; 1098 } 1099 mIsMultiParty = mOriginalConnection.isMultiparty(); 1100 1101 Bundle extrasToPut = new Bundle(); 1102 List<String> extrasToRemove = new ArrayList<>(); 1103 if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) { 1104 extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 1105 } else { 1106 extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL); 1107 } 1108 1109 if (shouldSetDisableAddCallExtra()) { 1110 extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true); 1111 } else { 1112 extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL); 1113 } 1114 putExtras(extrasToPut); 1115 removeExtras(extrasToRemove); 1116 1117 // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this 1118 // should be executed *after* the above setters have run. 1119 updateState(); 1120 if (mOriginalConnection == null) { 1121 Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " + 1122 originalConnection); 1123 } 1124 1125 fireOnOriginalConnectionConfigured(); 1126 } 1127 1128 /** 1129 * Filters the CNAP name to not include a list of names that are unhelpful to the user for 1130 * Caller ID purposes. 1131 */ 1132 private String filterCnapName(final String cnapName) { 1133 if (cnapName == null) { 1134 return null; 1135 } 1136 PersistableBundle carrierConfig = getCarrierConfig(); 1137 String[] filteredCnapNames = null; 1138 if (carrierConfig != null) { 1139 filteredCnapNames = carrierConfig.getStringArray( 1140 CarrierConfigManager.KEY_FILTERED_CNAP_NAMES_STRING_ARRAY); 1141 } 1142 if (filteredCnapNames != null) { 1143 long cnapNameMatches = Arrays.asList(filteredCnapNames) 1144 .stream() 1145 .filter(filteredCnapName -> filteredCnapName.equals(cnapName.toUpperCase())) 1146 .count(); 1147 if (cnapNameMatches > 0) { 1148 Log.i(this, "filterCnapName: Filtered CNAP Name: " + cnapName); 1149 return ""; 1150 } 1151 } 1152 return cnapName; 1153 } 1154 1155 /** 1156 * Sets the EXTRA_CALL_TECHNOLOGY_TYPE extra on the connection to report back to Telecom. 1157 */ 1158 private void setTechnologyTypeExtra() { 1159 if (getPhone() != null) { 1160 putExtra(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, getPhone().getPhoneType()); 1161 } 1162 } 1163 1164 private void refreshDisableAddCall() { 1165 if (shouldSetDisableAddCallExtra()) { 1166 putExtra(Connection.EXTRA_DISABLE_ADD_CALL, true); 1167 } else { 1168 removeExtras(Connection.EXTRA_DISABLE_ADD_CALL); 1169 } 1170 } 1171 1172 private boolean shouldSetDisableAddCallExtra() { 1173 if (mOriginalConnection == null) { 1174 return false; 1175 } 1176 boolean carrierShouldAllowAddCall = mOriginalConnection.shouldAllowAddCallDuringVideoCall(); 1177 if (carrierShouldAllowAddCall) { 1178 return false; 1179 } 1180 Phone phone = getPhone(); 1181 if (phone == null) { 1182 return false; 1183 } 1184 boolean isCurrentVideoCall = false; 1185 boolean wasVideoCall = false; 1186 boolean isVowifiEnabled = false; 1187 if (phone instanceof ImsPhone) { 1188 ImsPhone imsPhone = (ImsPhone) phone; 1189 if (imsPhone.getForegroundCall() != null 1190 && imsPhone.getForegroundCall().getImsCall() != null) { 1191 ImsCall call = imsPhone.getForegroundCall().getImsCall(); 1192 isCurrentVideoCall = call.isVideoCall(); 1193 wasVideoCall = call.wasVideoCall(); 1194 } 1195 1196 isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext()); 1197 } 1198 1199 if (isCurrentVideoCall) { 1200 return true; 1201 } else if (wasVideoCall && mIsWifi && !isVowifiEnabled) { 1202 return true; 1203 } 1204 return false; 1205 } 1206 1207 private boolean hasHighDefAudioProperty() { 1208 if (!mHasHighDefAudio) { 1209 return false; 1210 } 1211 1212 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 1213 1214 PersistableBundle b = getCarrierConfig(); 1215 boolean canWifiCallsBeHdAudio = 1216 b != null && b.getBoolean(CarrierConfigManager.KEY_WIFI_CALLS_CAN_BE_HD_AUDIO); 1217 boolean canVideoCallsBeHdAudio = 1218 b != null && b.getBoolean(CarrierConfigManager.KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO); 1219 boolean canGsmCdmaCallsBeHdAudio = 1220 b != null && b.getBoolean(CarrierConfigManager.KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO); 1221 boolean shouldDisplayHdAudio = 1222 b != null && b.getBoolean(CarrierConfigManager.KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL); 1223 1224 if (!shouldDisplayHdAudio) { 1225 return false; 1226 } 1227 1228 if (isGsmCdmaConnection() && !canGsmCdmaCallsBeHdAudio) { 1229 return false; 1230 } 1231 1232 if (isVideoCall && !canVideoCallsBeHdAudio) { 1233 return false; 1234 } 1235 1236 if (mIsWifi && !canWifiCallsBeHdAudio) { 1237 return false; 1238 } 1239 1240 return true; 1241 } 1242 1243 private boolean canHoldImsCalls() { 1244 PersistableBundle b = getCarrierConfig(); 1245 // Return true if the CarrierConfig is unavailable 1246 return !doesDeviceRespectHoldCarrierConfig() || b == null || 1247 b.getBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL); 1248 } 1249 1250 private PersistableBundle getCarrierConfig() { 1251 Phone phone = getPhone(); 1252 if (phone == null) { 1253 return null; 1254 } 1255 return PhoneGlobals.getInstance().getCarrierConfigForSubId(phone.getSubId()); 1256 } 1257 1258 private boolean canDeflectImsCalls() { 1259 PersistableBundle b = getCarrierConfig(); 1260 // Return false if the CarrierConfig is unavailable 1261 if (b != null) { 1262 return b.getBoolean( 1263 CarrierConfigManager.KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL) && 1264 isValidRingingCall(); 1265 } 1266 return false; 1267 } 1268 1269 /** 1270 * Determines if the device will respect the value of the 1271 * {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} configuration option. 1272 * 1273 * @return {@code false} if the device always supports holding IMS calls, {@code true} if it 1274 * will use {@link CarrierConfigManager#KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} to determine if 1275 * hold is supported. 1276 */ 1277 private boolean doesDeviceRespectHoldCarrierConfig() { 1278 Phone phone = getPhone(); 1279 if (phone == null) { 1280 return true; 1281 } 1282 return phone.getContext().getResources().getBoolean( 1283 com.android.internal.R.bool.config_device_respects_hold_carrier_config); 1284 } 1285 1286 /** 1287 * Whether the connection should be treated as an emergency. 1288 * @return {@code true} if the connection should be treated as an emergency call based 1289 * on the number dialed, {@code false} otherwise. 1290 */ 1291 protected boolean shouldTreatAsEmergencyCall() { 1292 return mTreatAsEmergencyCall; 1293 } 1294 1295 /** 1296 * Un-sets the underlying radio connection. 1297 */ 1298 void clearOriginalConnection() { 1299 if (mOriginalConnection != null) { 1300 if (getPhone() != null) { 1301 getPhone().unregisterForPreciseCallStateChanged(mHandler); 1302 getPhone().unregisterForRingbackTone(mHandler); 1303 getPhone().unregisterForHandoverStateChanged(mHandler); 1304 getPhone().unregisterForDisconnect(mHandler); 1305 getPhone().unregisterForSuppServiceNotification(mHandler); 1306 getPhone().unregisterForOnHoldTone(mHandler); 1307 getPhone().unregisterForInCallVoicePrivacyOn(mHandler); 1308 getPhone().unregisterForInCallVoicePrivacyOff(mHandler); 1309 } 1310 mOriginalConnection.removePostDialListener(mPostDialListener); 1311 mOriginalConnection.removeListener(mOriginalConnectionListener); 1312 mOriginalConnection = null; 1313 } 1314 } 1315 1316 protected void hangup(int telephonyDisconnectCode) { 1317 if (mOriginalConnection != null) { 1318 try { 1319 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 1320 // connection.hangup(). Without this change, the party originating the call 1321 // will not get sent to voicemail if the user opts to reject the call. 1322 if (isValidRingingCall()) { 1323 Call call = getCall(); 1324 if (call != null) { 1325 call.hangup(); 1326 } else { 1327 Log.w(this, "Attempting to hangup a connection without backing call."); 1328 } 1329 } else { 1330 // We still prefer to call connection.hangup() for non-ringing calls 1331 // in order to support hanging-up specific calls within a conference call. 1332 // If we invoked call.hangup() while in a conference, we would end up 1333 // hanging up the entire conference call instead of the specific connection. 1334 mOriginalConnection.hangup(); 1335 } 1336 } catch (CallStateException e) { 1337 Log.e(this, e, "Call to Connection.hangup failed with exception"); 1338 } 1339 } else { 1340 if (getState() == STATE_DISCONNECTED) { 1341 Log.i(this, "hangup called on an already disconnected call!"); 1342 close(); 1343 } else { 1344 // There are a few cases where mOriginalConnection has not been set yet. For 1345 // example, when the radio has to be turned on to make an emergency call, 1346 // mOriginalConnection could not be set for many seconds. 1347 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1348 android.telephony.DisconnectCause.LOCAL, 1349 "Local Disconnect before connection established.")); 1350 close(); 1351 } 1352 } 1353 } 1354 1355 com.android.internal.telephony.Connection getOriginalConnection() { 1356 return mOriginalConnection; 1357 } 1358 1359 protected Call getCall() { 1360 if (mOriginalConnection != null) { 1361 return mOriginalConnection.getCall(); 1362 } 1363 return null; 1364 } 1365 1366 Phone getPhone() { 1367 Call call = getCall(); 1368 if (call != null) { 1369 return call.getPhone(); 1370 } 1371 return null; 1372 } 1373 1374 private boolean hasMultipleTopLevelCalls() { 1375 int numCalls = 0; 1376 Phone phone = getPhone(); 1377 if (phone != null) { 1378 if (!phone.getRingingCall().isIdle()) { 1379 numCalls++; 1380 } 1381 if (!phone.getForegroundCall().isIdle()) { 1382 numCalls++; 1383 } 1384 if (!phone.getBackgroundCall().isIdle()) { 1385 numCalls++; 1386 } 1387 } 1388 return numCalls > 1; 1389 } 1390 1391 private com.android.internal.telephony.Connection getForegroundConnection() { 1392 if (getPhone() != null) { 1393 return getPhone().getForegroundCall().getEarliestConnection(); 1394 } 1395 return null; 1396 } 1397 1398 /** 1399 * Checks for and returns the list of conference participants 1400 * associated with this connection. 1401 */ 1402 public List<ConferenceParticipant> getConferenceParticipants() { 1403 if (mOriginalConnection == null) { 1404 Log.v(this, "Null mOriginalConnection, cannot get conf participants."); 1405 return null; 1406 } 1407 return mOriginalConnection.getConferenceParticipants(); 1408 } 1409 1410 /** 1411 * Checks to see the original connection corresponds to an active incoming call. Returns false 1412 * if there is no such actual call, or if the associated call is not incoming (See 1413 * {@link Call.State#isRinging}). 1414 */ 1415 private boolean isValidRingingCall() { 1416 if (getPhone() == null) { 1417 Log.v(this, "isValidRingingCall, phone is null"); 1418 return false; 1419 } 1420 1421 Call ringingCall = getPhone().getRingingCall(); 1422 if (!ringingCall.getState().isRinging()) { 1423 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 1424 return false; 1425 } 1426 1427 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 1428 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 1429 return false; 1430 } 1431 1432 Log.v(this, "isValidRingingCall, returning true"); 1433 return true; 1434 } 1435 1436 // Make sure the extras being passed into this method is a COPY of the original extras Bundle. 1437 // We do not want the extras to be cleared or modified during mOriginalConnectionExtras.putAll 1438 // below. 1439 protected void updateExtras(Bundle extras) { 1440 if (mOriginalConnection != null) { 1441 if (extras != null) { 1442 // Check if extras have changed and need updating. 1443 if (!areBundlesEqual(mOriginalConnectionExtras, extras)) { 1444 if (Log.DEBUG) { 1445 Log.d(TelephonyConnection.this, "Updating extras:"); 1446 for (String key : extras.keySet()) { 1447 Object value = extras.get(key); 1448 if (value instanceof String) { 1449 Log.d(this, "updateExtras Key=" + Log.pii(key) + 1450 " value=" + Log.pii((String)value)); 1451 } 1452 } 1453 } 1454 mOriginalConnectionExtras.clear(); 1455 1456 mOriginalConnectionExtras.putAll(extras); 1457 1458 // Remap any string extras that have a remapping defined. 1459 for (String key : mOriginalConnectionExtras.keySet()) { 1460 if (sExtrasMap.containsKey(key)) { 1461 String newKey = sExtrasMap.get(key); 1462 mOriginalConnectionExtras.putString(newKey, extras.getString(key)); 1463 mOriginalConnectionExtras.remove(key); 1464 } 1465 } 1466 1467 // Ensure extras are propagated to Telecom. 1468 putExtras(mOriginalConnectionExtras); 1469 } else { 1470 Log.d(this, "Extras update not required"); 1471 } 1472 } else { 1473 Log.d(this, "updateExtras extras: " + Log.pii(extras)); 1474 } 1475 } 1476 } 1477 1478 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 1479 if (extras == null || newExtras == null) { 1480 return extras == newExtras; 1481 } 1482 1483 if (extras.size() != newExtras.size()) { 1484 return false; 1485 } 1486 1487 for(String key : extras.keySet()) { 1488 if (key != null) { 1489 final Object value = extras.get(key); 1490 final Object newValue = newExtras.get(key); 1491 if (!Objects.equals(value, newValue)) { 1492 return false; 1493 } 1494 } 1495 } 1496 return true; 1497 } 1498 1499 void setStateOverride(Call.State state) { 1500 mIsStateOverridden = true; 1501 mConnectionOverriddenState = state; 1502 // Need to keep track of the original connection's state before override. 1503 mOriginalConnectionState = mOriginalConnection.getState(); 1504 updateStateInternal(); 1505 } 1506 1507 void resetStateOverride() { 1508 mIsStateOverridden = false; 1509 updateStateInternal(); 1510 } 1511 1512 void updateStateInternal() { 1513 if (mOriginalConnection == null) { 1514 return; 1515 } 1516 Call.State newState; 1517 // If the state is overridden and the state of the original connection hasn't changed since, 1518 // then we continue in the overridden state, else we go to the original connection's state. 1519 if (mIsStateOverridden && mOriginalConnectionState == mOriginalConnection.getState()) { 1520 newState = mConnectionOverriddenState; 1521 } else { 1522 newState = mOriginalConnection.getState(); 1523 } 1524 int cause = mOriginalConnection.getDisconnectCause(); 1525 Log.v(this, "Update state from %s to %s for %s", mConnectionState, newState, 1526 getTelecomCallId()); 1527 1528 if (mConnectionState != newState) { 1529 mConnectionState = newState; 1530 switch (newState) { 1531 case IDLE: 1532 break; 1533 case ACTIVE: 1534 setActiveInternal(); 1535 break; 1536 case HOLDING: 1537 setOnHold(); 1538 break; 1539 case DIALING: 1540 case ALERTING: 1541 if (mOriginalConnection != null && mOriginalConnection.isPulledCall()) { 1542 setPulling(); 1543 } else { 1544 setDialing(); 1545 } 1546 break; 1547 case INCOMING: 1548 case WAITING: 1549 setRinging(); 1550 break; 1551 case DISCONNECTED: 1552 if (shouldTreatAsEmergencyCall() 1553 && (cause 1554 == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE 1555 || cause 1556 == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE)) { 1557 // We can get into a situation where the radio wants us to redial the 1558 // same emergency call on the other available slot. This will not set 1559 // the state to disconnected and will instead tell the 1560 // TelephonyConnectionService to 1561 // create a new originalConnection using the new Slot. 1562 fireOnOriginalConnectionRetryDial(cause 1563 == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE); 1564 } else { 1565 int preciseDisconnectCause = CallFailCause.NOT_VALID; 1566 if (mShowPreciseFailedCause) { 1567 preciseDisconnectCause = 1568 mOriginalConnection.getPreciseDisconnectCause(); 1569 } 1570 setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1571 mOriginalConnection.getDisconnectCause(), 1572 preciseDisconnectCause, 1573 mOriginalConnection.getVendorDisconnectCause())); 1574 close(); 1575 } 1576 break; 1577 case DISCONNECTING: 1578 break; 1579 } 1580 } 1581 } 1582 1583 void updateState() { 1584 if (mOriginalConnection == null) { 1585 return; 1586 } 1587 1588 updateStateInternal(); 1589 updateStatusHints(); 1590 updateConnectionCapabilities(); 1591 updateConnectionProperties(); 1592 updateAddress(); 1593 updateMultiparty(); 1594 refreshDisableAddCall(); 1595 } 1596 1597 /** 1598 * Checks for changes to the multiparty bit. If a conference has started, informs listeners. 1599 */ 1600 private void updateMultiparty() { 1601 if (mOriginalConnection == null) { 1602 return; 1603 } 1604 1605 if (mIsMultiParty != mOriginalConnection.isMultiparty()) { 1606 mIsMultiParty = mOriginalConnection.isMultiparty(); 1607 1608 if (mIsMultiParty) { 1609 notifyConferenceStarted(); 1610 } 1611 } 1612 } 1613 1614 /** 1615 * Handles a failure when merging calls into a conference. 1616 * {@link com.android.internal.telephony.Connection.Listener#onConferenceMergedFailed()} 1617 * listener. 1618 */ 1619 private void handleConferenceMergeFailed(){ 1620 mHandler.obtainMessage(MSG_CONFERENCE_MERGE_FAILED).sendToTarget(); 1621 } 1622 1623 /** 1624 * Handles requests to update the multiparty state received via the 1625 * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)} 1626 * listener. 1627 * <p> 1628 * Note: We post this to the mHandler to ensure that if a conference must be created as a 1629 * result of the multiparty state change, the conference creation happens on the correct 1630 * thread. This ensures that the thread check in 1631 * {@link com.android.internal.telephony.Phone#checkCorrectThread(android.os.Handler)} 1632 * does not fire. 1633 * 1634 * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise. 1635 */ 1636 private void handleMultipartyStateChange(boolean isMultiParty) { 1637 Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N"); 1638 mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget(); 1639 } 1640 1641 private void setActiveInternal() { 1642 if (getState() == STATE_ACTIVE) { 1643 Log.w(this, "Should not be called if this is already ACTIVE"); 1644 return; 1645 } 1646 1647 // When we set a call to active, we need to make sure that there are no other active 1648 // calls. However, the ordering of state updates to connections can be non-deterministic 1649 // since all connections register for state changes on the phone independently. 1650 // To "optimize", we check here to see if there already exists any active calls. If so, 1651 // we issue an update for those calls first to make sure we only have one top-level 1652 // active call. 1653 if (getConnectionService() != null) { 1654 for (Connection current : getConnectionService().getAllConnections()) { 1655 if (current != this && current instanceof TelephonyConnection) { 1656 TelephonyConnection other = (TelephonyConnection) current; 1657 if (other.getState() == STATE_ACTIVE) { 1658 other.updateState(); 1659 } 1660 } 1661 } 1662 } 1663 setActive(); 1664 } 1665 1666 private void close() { 1667 Log.v(this, "close"); 1668 clearOriginalConnection(); 1669 destroy(); 1670 } 1671 1672 /** 1673 * Determines if the current connection is video capable. 1674 * 1675 * A connection is deemed to be video capable if the original connection capabilities state that 1676 * both local and remote video is supported. 1677 * 1678 * @return {@code true} if the connection is video capable, {@code false} otherwise. 1679 */ 1680 private boolean isVideoCapable() { 1681 return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL) 1682 && can(mOriginalConnectionCapabilities, 1683 Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 1684 } 1685 1686 /** 1687 * Determines if the current connection is an external connection. 1688 * 1689 * A connection is deemed to be external if the original connection capabilities state that it 1690 * is. 1691 * 1692 * @return {@code true} if the connection is external, {@code false} otherwise. 1693 */ 1694 private boolean isExternalConnection() { 1695 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION); 1696 } 1697 1698 /** 1699 * Determines if the current connection has RTT enabled. 1700 */ 1701 private boolean isRtt() { 1702 return mOriginalConnection != null 1703 && mOriginalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS 1704 && mOriginalConnection instanceof ImsPhoneConnection 1705 && ((ImsPhoneConnection) mOriginalConnection).isRttEnabledForCall(); 1706 } 1707 1708 /** 1709 * Determines if the current connection is pullable. 1710 * 1711 * A connection is deemed to be pullable if the original connection capabilities state that it 1712 * is. 1713 * 1714 * @return {@code true} if the connection is pullable, {@code false} otherwise. 1715 */ 1716 private boolean isPullable() { 1717 return can(mOriginalConnectionCapabilities, Capability.IS_EXTERNAL_CONNECTION) 1718 && can(mOriginalConnectionCapabilities, Capability.IS_PULLABLE); 1719 } 1720 1721 /** 1722 * Sets whether or not CDMA enhanced call privacy is enabled for this connection. 1723 */ 1724 private void setCdmaVoicePrivacy(boolean isEnabled) { 1725 if(mIsCdmaVoicePrivacyEnabled != isEnabled) { 1726 mIsCdmaVoicePrivacyEnabled = isEnabled; 1727 updateConnectionProperties(); 1728 } 1729 } 1730 1731 /** 1732 * Applies capabilities specific to conferences termination to the 1733 * {@code ConnectionCapabilities} bit-mask. 1734 * 1735 * @param capabilities The {@code ConnectionCapabilities} bit-mask. 1736 * @return The capabilities with the IMS conference capabilities applied. 1737 */ 1738 private int applyConferenceTerminationCapabilities(int capabilities) { 1739 int currentCapabilities = capabilities; 1740 1741 // An IMS call cannot be individually disconnected or separated from its parent conference. 1742 // If the call was IMS, even if it hands over to GMS, these capabilities are not supported. 1743 if (!mWasImsConnection) { 1744 currentCapabilities |= CAPABILITY_DISCONNECT_FROM_CONFERENCE; 1745 currentCapabilities |= CAPABILITY_SEPARATE_FROM_CONFERENCE; 1746 } 1747 1748 return currentCapabilities; 1749 } 1750 1751 /** 1752 * Stores the new original connection capabilities, and applies them to the current connection, 1753 * notifying any listeners as necessary. 1754 * 1755 * @param connectionCapabilities The original connection capabilties. 1756 */ 1757 public void setOriginalConnectionCapabilities(int connectionCapabilities) { 1758 mOriginalConnectionCapabilities = connectionCapabilities; 1759 updateConnectionCapabilities(); 1760 updateConnectionProperties(); 1761 } 1762 1763 /** 1764 * Called to apply the capabilities present in the {@link #mOriginalConnection} to this 1765 * {@link Connection}. Provides a mapping between the capabilities present in the original 1766 * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in 1767 * this {@link Connection}. 1768 * 1769 * @param capabilities The capabilities bitmask from the {@link Connection}. 1770 * @return the capabilities bitmask with the original connection capabilities remapped and 1771 * applied. 1772 */ 1773 public int applyOriginalConnectionCapabilities(int capabilities) { 1774 // We only support downgrading to audio if both the remote and local side support 1775 // downgrading to audio. 1776 boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities, 1777 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL | 1778 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 1779 capabilities = changeBitmask(capabilities, 1780 CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio); 1781 1782 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, 1783 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL)); 1784 1785 capabilities = changeBitmask(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, 1786 can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); 1787 1788 return capabilities; 1789 } 1790 1791 /** 1792 * Sets whether the call is using wifi. Used when rebuilding the capabilities to set or unset 1793 * the {@link Connection#PROPERTY_WIFI} property. 1794 */ 1795 public void setWifi(boolean isWifi) { 1796 mIsWifi = isWifi; 1797 updateConnectionProperties(); 1798 updateStatusHints(); 1799 refreshDisableAddCall(); 1800 } 1801 1802 /** 1803 * Whether the call is using wifi. 1804 */ 1805 boolean isWifi() { 1806 return mIsWifi; 1807 } 1808 1809 /** 1810 * @return {@code true} if this is an outgoing call, {@code false} otherwise. 1811 */ 1812 boolean isOutgoingCall() { 1813 return mIsOutgoing; 1814 } 1815 1816 /** 1817 * Sets the current call audio quality. Used during rebuild of the properties 1818 * to set or unset the {@link Connection#PROPERTY_HIGH_DEF_AUDIO} property. 1819 * 1820 * @param audioQuality The audio quality. 1821 */ 1822 public void setAudioQuality(int audioQuality) { 1823 mHasHighDefAudio = audioQuality == 1824 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1825 updateConnectionProperties(); 1826 } 1827 1828 void resetStateForConference() { 1829 if (getState() == Connection.STATE_HOLDING) { 1830 resetStateOverride(); 1831 } 1832 } 1833 1834 boolean setHoldingForConference() { 1835 if (getState() == Connection.STATE_ACTIVE) { 1836 setStateOverride(Call.State.HOLDING); 1837 return true; 1838 } 1839 return false; 1840 } 1841 1842 public void setRttTextStream(RttTextStream s) { 1843 mRttTextStream = s; 1844 } 1845 1846 public RttTextStream getRttTextStream() { 1847 return mRttTextStream; 1848 } 1849 1850 /** 1851 * For video calls, sets whether this connection supports pausing the outgoing video for the 1852 * call using the {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1853 * 1854 * @param isVideoPauseSupported {@code true} if pause state supported, {@code false} otherwise. 1855 */ 1856 public void setVideoPauseSupported(boolean isVideoPauseSupported) { 1857 mIsVideoPauseSupported = isVideoPauseSupported; 1858 } 1859 1860 /** 1861 * @return {@code true} if this connection supports pausing the outgoing video using the 1862 * {@link android.telecom.VideoProfile#STATE_PAUSED} VideoState. 1863 */ 1864 public boolean getVideoPauseSupported() { 1865 return mIsVideoPauseSupported; 1866 } 1867 1868 /** 1869 * Sets whether this connection supports conference calling. 1870 * @param isConferenceSupported {@code true} if conference calling is supported by this 1871 * connection, {@code false} otherwise. 1872 */ 1873 public void setConferenceSupported(boolean isConferenceSupported) { 1874 mIsConferenceSupported = isConferenceSupported; 1875 } 1876 1877 /** 1878 * @return {@code true} if this connection supports merging calls into a conference. 1879 */ 1880 public boolean isConferenceSupported() { 1881 return mIsConferenceSupported; 1882 } 1883 1884 /** 1885 * Sets whether managing conference call is supported after this connection being a part of a 1886 * Ims conference. 1887 * 1888 * @param isManageImsConferenceCallSupported {@code true} if manage conference calling is 1889 * supported after this connection being a part of a IMS conference, 1890 * {@code false} otherwise. 1891 */ 1892 public void setManageImsConferenceCallSupported(boolean isManageImsConferenceCallSupported) { 1893 mIsManageImsConferenceCallSupported = isManageImsConferenceCallSupported; 1894 } 1895 1896 /** 1897 * @return {@code true} if manage conference calling is supported after this connection being a 1898 * part of a IMS conference. 1899 */ 1900 public boolean isManageImsConferenceCallSupported() { 1901 return mIsManageImsConferenceCallSupported; 1902 } 1903 1904 /** 1905 * Sets whether this connection supports showing precise call disconnect cause. 1906 * @param showPreciseFailedCause {@code true} if showing precise call 1907 * disconnect cause is supported by this connection, {@code false} otherwise. 1908 */ 1909 public void setShowPreciseFailedCause(boolean showPreciseFailedCause) { 1910 mShowPreciseFailedCause = showPreciseFailedCause; 1911 } 1912 1913 /** 1914 * Whether the original connection is an IMS connection. 1915 * @return {@code True} if the original connection is an IMS connection, {@code false} 1916 * otherwise. 1917 */ 1918 protected boolean isImsConnection() { 1919 com.android.internal.telephony.Connection originalConnection = getOriginalConnection(); 1920 return originalConnection != null && 1921 originalConnection.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 1922 } 1923 1924 /** 1925 * Whether the original connection is an GSM/CDMA connection. 1926 * @return {@code True} if the original connection is an GSM/CDMA connection, {@code false} 1927 * otherwise. 1928 */ 1929 protected boolean isGsmCdmaConnection() { 1930 Phone phone = getPhone(); 1931 if (phone != null) { 1932 switch (phone.getPhoneType()) { 1933 case PhoneConstants.PHONE_TYPE_GSM: 1934 case PhoneConstants.PHONE_TYPE_CDMA: 1935 return true; 1936 default: 1937 return false; 1938 } 1939 } 1940 return false; 1941 } 1942 1943 /** 1944 * Whether the original connection was ever an IMS connection, either before or now. 1945 * @return {@code True} if the original connection was ever an IMS connection, {@code false} 1946 * otherwise. 1947 */ 1948 public boolean wasImsConnection() { 1949 return mWasImsConnection; 1950 } 1951 1952 boolean getIsUsingAssistedDialing() { 1953 return mIsUsingAssistedDialing; 1954 } 1955 1956 void setIsUsingAssistedDialing(Boolean isUsingAssistedDialing) { 1957 mIsUsingAssistedDialing = isUsingAssistedDialing; 1958 updateConnectionProperties(); 1959 } 1960 1961 private static Uri getAddressFromNumber(String number) { 1962 // Address can be null for blocked calls. 1963 if (number == null) { 1964 number = ""; 1965 } 1966 return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1967 } 1968 1969 /** 1970 * Changes a capabilities bit-mask to add or remove a capability. 1971 * 1972 * @param bitmask The bit-mask. 1973 * @param bitfield The bit-field to change. 1974 * @param enabled Whether the bit-field should be set or removed. 1975 * @return The bit-mask with the bit-field changed. 1976 */ 1977 private int changeBitmask(int bitmask, int bitfield, boolean enabled) { 1978 if (enabled) { 1979 return bitmask | bitfield; 1980 } else { 1981 return bitmask & ~bitfield; 1982 } 1983 } 1984 1985 private void updateStatusHints() { 1986 boolean isIncoming = isValidRingingCall(); 1987 if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) { 1988 int labelId = isIncoming 1989 ? R.string.status_hint_label_incoming_wifi_call 1990 : R.string.status_hint_label_wifi_call; 1991 1992 Context context = getPhone().getContext(); 1993 setStatusHints(new StatusHints( 1994 context.getString(labelId), 1995 Icon.createWithResource( 1996 context.getResources(), 1997 R.drawable.ic_signal_wifi_4_bar_24dp), 1998 null /* extras */)); 1999 } else { 2000 setStatusHints(null); 2001 } 2002 } 2003 2004 /** 2005 * Register a listener for {@link TelephonyConnection} specific triggers. 2006 * @param l The instance of the listener to add 2007 * @return The connection being listened to 2008 */ 2009 public final TelephonyConnection addTelephonyConnectionListener(TelephonyConnectionListener l) { 2010 mTelephonyListeners.add(l); 2011 // If we already have an original connection, let's call back immediately. 2012 // This would be the case for incoming calls. 2013 if (mOriginalConnection != null) { 2014 fireOnOriginalConnectionConfigured(); 2015 } 2016 return this; 2017 } 2018 2019 /** 2020 * Remove a listener for {@link TelephonyConnection} specific triggers. 2021 * @param l The instance of the listener to remove 2022 * @return The connection being listened to 2023 */ 2024 public final TelephonyConnection removeTelephonyConnectionListener( 2025 TelephonyConnectionListener l) { 2026 if (l != null) { 2027 mTelephonyListeners.remove(l); 2028 } 2029 return this; 2030 } 2031 2032 @Override 2033 public void setHoldable(boolean isHoldable) { 2034 mIsHoldable = isHoldable; 2035 updateConnectionCapabilities(); 2036 } 2037 2038 @Override 2039 public boolean isChildHoldable() { 2040 return getConference() != null; 2041 } 2042 2043 public boolean isHoldable() { 2044 return mIsHoldable; 2045 } 2046 2047 /** 2048 * Fire a callback to the various listeners for when the original connection is 2049 * set in this {@link TelephonyConnection} 2050 */ 2051 private final void fireOnOriginalConnectionConfigured() { 2052 for (TelephonyConnectionListener l : mTelephonyListeners) { 2053 l.onOriginalConnectionConfigured(this); 2054 } 2055 } 2056 2057 private final void fireOnOriginalConnectionRetryDial(boolean isPermanentFailure) { 2058 for (TelephonyConnectionListener l : mTelephonyListeners) { 2059 l.onOriginalConnectionRetry(this, isPermanentFailure); 2060 } 2061 } 2062 2063 /** 2064 * Handles exiting ECM mode. 2065 */ 2066 protected void handleExitedEcmMode() { 2067 updateConnectionProperties(); 2068 } 2069 2070 /** 2071 * Determines whether the connection supports conference calling. A connection supports 2072 * conference calling if it: 2073 * 1. Is not an emergency call. 2074 * 2. Carrier supports conference calls. 2075 * 3. If call is a video call, carrier supports video conference calls. 2076 * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls. 2077 */ 2078 private void refreshConferenceSupported() { 2079 boolean isVideoCall = VideoProfile.isVideo(getVideoState()); 2080 Phone phone = getPhone(); 2081 if (phone == null) { 2082 Log.w(this, "refreshConferenceSupported = false; phone is null"); 2083 if (isConferenceSupported()) { 2084 setConferenceSupported(false); 2085 notifyConferenceSupportedChanged(false); 2086 } 2087 return; 2088 } 2089 2090 boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS; 2091 boolean isVoWifiEnabled = false; 2092 if (isIms) { 2093 ImsPhone imsPhone = (ImsPhone) phone; 2094 isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext()); 2095 } 2096 PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils 2097 .makePstnPhoneAccountHandle(phone.getDefaultPhone()) 2098 : PhoneUtils.makePstnPhoneAccountHandle(phone); 2099 TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry 2100 .getInstance(getPhone().getContext()); 2101 boolean isConferencingSupported = telecomAccountRegistry 2102 .isMergeCallSupported(phoneAccountHandle); 2103 boolean isImsConferencingSupported = telecomAccountRegistry 2104 .isMergeImsCallSupported(phoneAccountHandle); 2105 mIsCarrierVideoConferencingSupported = telecomAccountRegistry 2106 .isVideoConferencingSupported(phoneAccountHandle); 2107 boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry 2108 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle); 2109 2110 Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isImsConfSupp=%b, " + 2111 "isVidConfSupp=%b, isMergeOfWifiAllowed=%b, " + 2112 "isWifi=%b, isVoWifiEnabled=%b", 2113 isConferencingSupported, isImsConferencingSupported, 2114 mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, 2115 isWifi(), isVoWifiEnabled); 2116 boolean isConferenceSupported = true; 2117 if (mTreatAsEmergencyCall) { 2118 isConferenceSupported = false; 2119 Log.d(this, "refreshConferenceSupported = false; emergency call"); 2120 } else if (!isConferencingSupported || isIms && !isImsConferencingSupported) { 2121 isConferenceSupported = false; 2122 Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf."); 2123 } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) { 2124 isConferenceSupported = false; 2125 Log.d(this, "refreshConferenceSupported = false; video conf not supported."); 2126 } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) { 2127 isConferenceSupported = false; 2128 Log.d(this, 2129 "refreshConferenceSupported = false; can't merge wifi calls when voWifi off."); 2130 } else { 2131 Log.d(this, "refreshConferenceSupported = true."); 2132 } 2133 2134 if (isConferenceSupported != isConferenceSupported()) { 2135 setConferenceSupported(isConferenceSupported); 2136 notifyConferenceSupportedChanged(isConferenceSupported); 2137 } 2138 } 2139 /** 2140 * Provides a mapping from extras keys which may be found in the 2141 * {@link com.android.internal.telephony.Connection} to their equivalents defined in 2142 * {@link android.telecom.Connection}. 2143 * 2144 * @return Map containing key mappings. 2145 */ 2146 private static Map<String, String> createExtrasMap() { 2147 Map<String, String> result = new HashMap<String, String>(); 2148 result.put(ImsCallProfile.EXTRA_CHILD_NUMBER, 2149 android.telecom.Connection.EXTRA_CHILD_ADDRESS); 2150 result.put(ImsCallProfile.EXTRA_DISPLAY_TEXT, 2151 android.telecom.Connection.EXTRA_CALL_SUBJECT); 2152 return Collections.unmodifiableMap(result); 2153 } 2154 2155 /** 2156 * Creates a string representation of this {@link TelephonyConnection}. Primarily intended for 2157 * use in log statements. 2158 * 2159 * @return String representation of the connection. 2160 */ 2161 @Override 2162 public String toString() { 2163 StringBuilder sb = new StringBuilder(); 2164 sb.append("[TelephonyConnection objId:"); 2165 sb.append(System.identityHashCode(this)); 2166 sb.append(" telecomCallID:"); 2167 sb.append(getTelecomCallId()); 2168 sb.append(" type:"); 2169 if (isImsConnection()) { 2170 sb.append("ims"); 2171 } else if (this instanceof com.android.services.telephony.GsmConnection) { 2172 sb.append("gsm"); 2173 } else if (this instanceof CdmaConnection) { 2174 sb.append("cdma"); 2175 } 2176 sb.append(" state:"); 2177 sb.append(Connection.stateToString(getState())); 2178 sb.append(" capabilities:"); 2179 sb.append(capabilitiesToString(getConnectionCapabilities())); 2180 sb.append(" properties:"); 2181 sb.append(propertiesToString(getConnectionProperties())); 2182 sb.append(" address:"); 2183 sb.append(Log.pii(getAddress())); 2184 sb.append(" originalConnection:"); 2185 sb.append(mOriginalConnection); 2186 sb.append(" partOfConf:"); 2187 if (getConference() == null) { 2188 sb.append("N"); 2189 } else { 2190 sb.append("Y"); 2191 } 2192 sb.append(" confSupported:"); 2193 sb.append(mIsConferenceSupported ? "Y" : "N"); 2194 sb.append("]"); 2195 return sb.toString(); 2196 } 2197 } 2198