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