TelephonyConnection.java revision 8256cd0003669729d08d0b6534a8c8e7d0e054e0
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.services.telephony; 18 19import android.net.Uri; 20import android.os.AsyncResult; 21import android.os.Handler; 22import android.os.Message; 23import android.telecomm.AudioState; 24import android.telecomm.Connection; 25import android.telecomm.PhoneCapabilities; 26import android.telephony.DisconnectCause; 27 28import com.android.internal.telephony.Call; 29import com.android.internal.telephony.CallStateException; 30import com.android.internal.telephony.Connection.PostDialListener; 31import com.android.internal.telephony.Phone; 32 33import java.lang.Override; 34import java.util.Objects; 35 36/** 37 * Base class for CDMA and GSM connections. 38 */ 39abstract class TelephonyConnection extends Connection { 40 private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1; 41 private static final int MSG_RINGBACK_TONE = 2; 42 private static final int MSG_HANDOVER_STATE_CHANGED = 3; 43 44 private final Handler mHandler = new Handler() { 45 @Override 46 public void handleMessage(Message msg) { 47 switch (msg.what) { 48 case MSG_PRECISE_CALL_STATE_CHANGED: 49 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 50 updateState(); 51 break; 52 case MSG_HANDOVER_STATE_CHANGED: 53 Log.v(TelephonyConnection.this, "MSG_HANDOVER_STATE_CHANGED"); 54 AsyncResult ar = (AsyncResult) msg.obj; 55 com.android.internal.telephony.Connection connection = 56 (com.android.internal.telephony.Connection) ar.result; 57 setOriginalConnection(connection); 58 break; 59 case MSG_RINGBACK_TONE: 60 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 61 // TODO: This code assumes that there is only one connection in the foreground 62 // call, in other words, it punts on network-mediated conference calling. 63 if (getOriginalConnection() != getForegroundConnection()) { 64 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 65 "not foreground connection, skipping"); 66 return; 67 } 68 setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result); 69 break; 70 } 71 } 72 }; 73 74 private final PostDialListener mPostDialListener = new PostDialListener() { 75 @Override 76 public void onPostDialWait() { 77 Log.v(TelephonyConnection.this, "onPostDialWait"); 78 if (mOriginalConnection != null) { 79 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 80 } 81 } 82 }; 83 84 /** 85 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 86 */ 87 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 88 new com.android.internal.telephony.Connection.ListenerBase() { 89 @Override 90 public void onVideoStateChanged(int videoState) { 91 setVideoState(videoState); 92 } 93 94 /** 95 * The {@link com.android.internal.telephony.Connection} has reported a change in local 96 * video capability. 97 * 98 * @param capable True if capable. 99 */ 100 @Override 101 public void onLocalVideoCapabilityChanged(boolean capable) { 102 setLocalVideoCapable(capable); 103 } 104 105 /** 106 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 107 * video capability. 108 * 109 * @param capable True if capable. 110 */ 111 @Override 112 public void onRemoteVideoCapabilityChanged(boolean capable) { 113 setRemoteVideoCapable(capable); 114 } 115 116 /** 117 * The {@link com.android.internal.telephony.Connection} has reported a change in the 118 * video call provider. 119 * 120 * @param videoProvider The video call provider. 121 */ 122 @Override 123 public void onVideoProviderChanged(VideoProvider videoProvider) { 124 setVideoProvider(videoProvider); 125 } 126 127 /** 128 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 129 * audio quality for the current call. 130 * 131 * @param audioQuality The audio quality. 132 */ 133 @Override 134 public void onAudioQualityChanged(int audioQuality) { 135 setAudioQuality(audioQuality); 136 } 137 }; 138 139 private com.android.internal.telephony.Connection mOriginalConnection; 140 private Call.State mOriginalConnectionState = Call.State.IDLE; 141 142 /** 143 * Determines if the {@link TelephonyConnection} has local video capabilities. 144 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 145 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 146 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 147 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 148 * layer. 149 */ 150 private boolean mLocalVideoCapable; 151 152 /** 153 * Determines if the {@link TelephonyConnection} has remote video capabilities. 154 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 155 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 156 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 157 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 158 * layer. 159 */ 160 private boolean mRemoteVideoCapable; 161 162 /** 163 * Determines the current audio quality for the {@link TelephonyConnection}. 164 * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate 165 * whether a call has the {@link android.telecomm.CallCapabilities#VoLTE} capability. 166 */ 167 private int mAudioQuality; 168 169 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 170 if (originalConnection != null) { 171 setOriginalConnection(originalConnection); 172 } 173 } 174 175 @Override 176 public void onSetAudioState(AudioState audioState) { 177 // TODO: update TTY mode. 178 if (getPhone() != null) { 179 getPhone().setEchoSuppressionEnabled(); 180 } 181 } 182 183 @Override 184 public void onSetState(int state) { 185 Log.v(this, "onSetState, state: " + Connection.stateToString(state)); 186 } 187 188 @Override 189 public void onDisconnect() { 190 Log.v(this, "onDisconnect"); 191 hangup(DisconnectCause.LOCAL); 192 } 193 194 @Override 195 public void onSeparate() { 196 Log.v(this, "onSeparate"); 197 if (mOriginalConnection != null) { 198 try { 199 mOriginalConnection.separate(); 200 } catch (CallStateException e) { 201 Log.e(this, e, "Call to Connection.separate failed with exception"); 202 } 203 } 204 } 205 206 @Override 207 public void onAbort() { 208 Log.v(this, "onAbort"); 209 hangup(DisconnectCause.LOCAL); 210 } 211 212 @Override 213 public void onHold() { 214 performHold(); 215 } 216 217 @Override 218 public void onUnhold() { 219 performUnhold(); 220 } 221 222 @Override 223 public void onAnswer(int videoState) { 224 Log.v(this, "onAnswer"); 225 // TODO: Tons of hairy logic is missing here around multiple active calls on 226 // CDMA devices. See {@link CallManager.acceptCall}. 227 228 if (isValidRingingCall() && getPhone() != null) { 229 try { 230 getPhone().acceptCall(videoState); 231 } catch (CallStateException e) { 232 Log.e(this, e, "Failed to accept call."); 233 } 234 } 235 } 236 237 @Override 238 public void onReject() { 239 Log.v(this, "onReject"); 240 if (isValidRingingCall()) { 241 hangup(DisconnectCause.INCOMING_REJECTED); 242 } 243 super.onReject(); 244 } 245 246 @Override 247 public void onPostDialContinue(boolean proceed) { 248 Log.v(this, "onPostDialContinue, proceed: " + proceed); 249 if (mOriginalConnection != null) { 250 if (proceed) { 251 mOriginalConnection.proceedAfterWaitChar(); 252 } else { 253 mOriginalConnection.cancelPostDial(); 254 } 255 } 256 } 257 258 @Override 259 public void onPhoneAccountClicked() { 260 Log.v(this, "onPhoneAccountClicked"); 261 } 262 263 public void performHold() { 264 Log.v(this, "performHold"); 265 // TODO: Can dialing calls be put on hold as well since they take up the 266 // foreground call slot? 267 if (Call.State.ACTIVE == mOriginalConnectionState) { 268 Log.v(this, "Holding active call"); 269 try { 270 Phone phone = mOriginalConnection.getCall().getPhone(); 271 Call ringingCall = phone.getRingingCall(); 272 273 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 274 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 275 // a call on hold while a call-waiting call exists, it'll end up accepting the 276 // call-waiting call, which is bad if that was not the user's intention. We are 277 // cheating here and simply skipping it because we know any attempt to hold a call 278 // while a call-waiting call is happening is likely a request from Telecomm prior to 279 // accepting the call-waiting call. 280 // TODO: Investigate a better solution. It would be great here if we 281 // could "fake" hold by silencing the audio and microphone streams for this call 282 // instead of actually putting it on hold. 283 if (ringingCall.getState() != Call.State.WAITING) { 284 phone.switchHoldingAndActive(); 285 } 286 287 // TODO: Cdma calls are slightly different. 288 } catch (CallStateException e) { 289 Log.e(this, e, "Exception occurred while trying to put call on hold."); 290 } 291 } else { 292 Log.w(this, "Cannot put a call that is not currently active on hold."); 293 } 294 } 295 296 public void performUnhold() { 297 Log.v(this, "performUnhold"); 298 if (Call.State.HOLDING == mOriginalConnectionState) { 299 try { 300 // Here's the deal--Telephony hold/unhold is weird because whenever there exists 301 // more than one call, one of them must always be active. In other words, if you 302 // have an active call and holding call, and you put the active call on hold, it 303 // will automatically activate the holding call. This is weird with how Telecomm 304 // sends its commands. When a user opts to "unhold" a background call, telecomm 305 // issues hold commands to all active calls, and then the unhold command to the 306 // background call. This means that we get two commands...each of which reduces to 307 // switchHoldingAndActive(). The result is that they simply cancel each other out. 308 // To fix this so that it works well with telecomm we add a minor hack. If we 309 // have one telephony call, everything works as normally expected. But if we have 310 // two or more calls, we will ignore all requests to "unhold" knowing that the hold 311 // requests already do what we want. If you've read up to this point, I'm very sorry 312 // that we are doing this. I didn't think of a better solution that wouldn't also 313 // make the Telecomm APIs very ugly. 314 315 if (!hasMultipleTopLevelCalls()) { 316 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 317 } else { 318 Log.i(this, "Skipping unhold command for %s", this); 319 } 320 } catch (CallStateException e) { 321 Log.e(this, e, "Exception occurred while trying to release call from hold."); 322 } 323 } else { 324 Log.w(this, "Cannot release a call that is not already on hold from hold."); 325 } 326 } 327 328 public void performConference(TelephonyConnection otherConnection) {} 329 330 protected abstract int buildCallCapabilities(); 331 332 protected final void updateCallCapabilities() { 333 int newCallCapabilities = buildCallCapabilities(); 334 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 335 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 336 337 if (getCallCapabilities() != newCallCapabilities) { 338 setCallCapabilities(newCallCapabilities); 339 } 340 } 341 342 protected final void updateHandle() { 343 updateCallCapabilities(); 344 if (mOriginalConnection != null) { 345 Uri handle = getHandleFromAddress(mOriginalConnection.getAddress()); 346 int presentation = mOriginalConnection.getNumberPresentation(); 347 if (!Objects.equals(handle, getHandle()) || 348 presentation != getHandlePresentation()) { 349 Log.v(this, "updateHandle, handle changed"); 350 setHandle(handle, presentation); 351 } 352 353 String name = mOriginalConnection.getCnapName(); 354 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 355 if (!Objects.equals(name, getCallerDisplayName()) || 356 namePresentation != getCallerDisplayNamePresentation()) { 357 Log.v(this, "updateHandle, caller display name changed"); 358 setCallerDisplayName(name, namePresentation); 359 } 360 } 361 } 362 363 void onRemovedFromCallService() { 364 // Subclass can override this to do cleanup. 365 } 366 367 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 368 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 369 if (mOriginalConnection != null) { 370 getPhone().unregisterForPreciseCallStateChanged(mHandler); 371 getPhone().unregisterForRingbackTone(mHandler); 372 getPhone().unregisterForHandoverStateChanged(mHandler); 373 } 374 mOriginalConnection = originalConnection; 375 getPhone().registerForPreciseCallStateChanged( 376 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 377 getPhone().registerForHandoverStateChanged( 378 mHandler, MSG_HANDOVER_STATE_CHANGED, null); 379 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 380 mOriginalConnection.addPostDialListener(mPostDialListener); 381 mOriginalConnection.addListener(mOriginalConnectionListener); 382 383 // Set video state and capabilities 384 setVideoState(mOriginalConnection.getVideoState()); 385 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 386 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 387 setVideoProvider(mOriginalConnection.getVideoProvider()); 388 setAudioQuality(mOriginalConnection.getAudioQuality()); 389 390 updateHandle(); 391 } 392 393 private void hangup(int disconnectCause) { 394 if (mOriginalConnection != null) { 395 try { 396 // Hanging up a ringing call requires that we invoke call.hangup() as opposed to 397 // connection.hangup(). Without this change, the party originating the call will not 398 // get sent to voicemail if the user opts to reject the call. 399 if (isValidRingingCall()) { 400 Call call = getCall(); 401 if (call != null) { 402 call.hangup(); 403 } else { 404 Log.w(this, "Attempting to hangup a connection without backing call."); 405 } 406 } else { 407 // We still prefer to call connection.hangup() for non-ringing calls in order 408 // to support hanging-up specific calls within a conference call. If we invoked 409 // call.hangup() while in a conference, we would end up hanging up the entire 410 // conference call instead of the specific connection. 411 mOriginalConnection.hangup(); 412 } 413 } catch (CallStateException e) { 414 Log.e(this, e, "Call to Connection.hangup failed with exception"); 415 } 416 } 417 418 // Set state deliberately since we are going to close() and will no longer be 419 // listening to state updates from mOriginalConnection 420 setDisconnected(disconnectCause, null); 421 close(); 422 } 423 424 com.android.internal.telephony.Connection getOriginalConnection() { 425 return mOriginalConnection; 426 } 427 428 protected Call getCall() { 429 if (mOriginalConnection != null) { 430 return mOriginalConnection.getCall(); 431 } 432 return null; 433 } 434 435 Phone getPhone() { 436 Call call = getCall(); 437 if (call != null) { 438 return call.getPhone(); 439 } 440 return null; 441 } 442 443 private boolean hasMultipleTopLevelCalls() { 444 int numCalls = 0; 445 Phone phone = getPhone(); 446 if (phone != null) { 447 if (!phone.getRingingCall().isIdle()) { 448 numCalls++; 449 } 450 if (!phone.getForegroundCall().isIdle()) { 451 numCalls++; 452 } 453 if (!phone.getBackgroundCall().isIdle()) { 454 numCalls++; 455 } 456 } 457 return numCalls > 1; 458 } 459 460 private com.android.internal.telephony.Connection getForegroundConnection() { 461 if (getPhone() != null) { 462 return getPhone().getForegroundCall().getEarliestConnection(); 463 } 464 return null; 465 } 466 467 /** 468 * Checks to see the original connection corresponds to an active incoming call. Returns false 469 * if there is no such actual call, or if the associated call is not incoming (See 470 * {@link Call.State#isRinging}). 471 */ 472 private boolean isValidRingingCall() { 473 if (getPhone() == null) { 474 Log.v(this, "isValidRingingCall, phone is null"); 475 return false; 476 } 477 478 Call ringingCall = getPhone().getRingingCall(); 479 if (!ringingCall.getState().isRinging()) { 480 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 481 return false; 482 } 483 484 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 485 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 486 return false; 487 } 488 489 Log.v(this, "isValidRingingCall, returning true"); 490 return true; 491 } 492 493 private void updateState() { 494 if (mOriginalConnection == null) { 495 return; 496 } 497 498 Call.State newState = mOriginalConnection.getState(); 499 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 500 if (mOriginalConnectionState != newState) { 501 mOriginalConnectionState = newState; 502 switch (newState) { 503 case IDLE: 504 break; 505 case ACTIVE: 506 setActive(); 507 break; 508 case HOLDING: 509 setOnHold(); 510 break; 511 case DIALING: 512 case ALERTING: 513 setDialing(); 514 break; 515 case INCOMING: 516 case WAITING: 517 setRinging(); 518 break; 519 case DISCONNECTED: 520 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 521 close(); 522 break; 523 case DISCONNECTING: 524 break; 525 } 526 } 527 updateCallCapabilities(); 528 updateHandle(); 529 } 530 531 private void close() { 532 Log.v(this, "close"); 533 if (getPhone() != null) { 534 getPhone().unregisterForPreciseCallStateChanged(mHandler); 535 getPhone().unregisterForRingbackTone(mHandler); 536 getPhone().unregisterForHandoverStateChanged(mHandler); 537 } 538 mOriginalConnection = null; 539 destroy(); 540 } 541 542 /** 543 * Applies the video capability states to the CallCapabilities bit-mask. 544 * 545 * @param capabilities The CallCapabilities bit-mask. 546 * @return The capabilities with video capabilities applied. 547 */ 548 private int applyVideoCapabilities(int capabilities) { 549 int currentCapabilities = capabilities; 550 if (mRemoteVideoCapable) { 551 currentCapabilities = applyCapability(currentCapabilities, 552 PhoneCapabilities.SUPPORTS_VT_REMOTE); 553 } else { 554 currentCapabilities = removeCapability(currentCapabilities, 555 PhoneCapabilities.SUPPORTS_VT_REMOTE); 556 } 557 558 if (mLocalVideoCapable) { 559 currentCapabilities = applyCapability(currentCapabilities, 560 PhoneCapabilities.SUPPORTS_VT_LOCAL); 561 } else { 562 currentCapabilities = removeCapability(currentCapabilities, 563 PhoneCapabilities.SUPPORTS_VT_LOCAL); 564 } 565 return currentCapabilities; 566 } 567 568 /** 569 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 570 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 571 * definition audio. 572 * 573 * @param callCapabilities The {@code CallCapabilities} bit-mask. 574 * @return The capabilities with the audio capabilities applied. 575 */ 576 private int applyAudioQualityCapabilities(int callCapabilities) { 577 int currentCapabilities = callCapabilities; 578 579 if (mAudioQuality == 580 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 581 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 582 } else { 583 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 584 } 585 586 return currentCapabilities; 587 } 588 589 /** 590 * Returns the local video capability state for the connection. 591 * 592 * @return {@code True} if the connection has local video capabilities. 593 */ 594 public boolean isLocalVideoCapable() { 595 return mLocalVideoCapable; 596 } 597 598 /** 599 * Returns the remote video capability state for the connection. 600 * 601 * @return {@code True} if the connection has remote video capabilities. 602 */ 603 public boolean isRemoteVideoCapable() { 604 return mRemoteVideoCapable; 605 } 606 607 /** 608 * Sets whether video capability is present locally. Used during rebuild of the 609 * {@link PhoneCapabilities} to set the video call capabilities. 610 * 611 * @param capable {@code True} if video capable. 612 */ 613 public void setLocalVideoCapable(boolean capable) { 614 mLocalVideoCapable = capable; 615 updateCallCapabilities(); 616 } 617 618 /** 619 * Sets whether video capability is present remotely. Used during rebuild of the 620 * {@link PhoneCapabilities} to set the video call capabilities. 621 * 622 * @param capable {@code True} if video capable. 623 */ 624 public void setRemoteVideoCapable(boolean capable) { 625 mRemoteVideoCapable = capable; 626 updateCallCapabilities(); 627 } 628 629 /** 630 * Sets the current call audio quality. Used during rebuild of the 631 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 632 * 633 * @param audioQuality The audio quality. 634 */ 635 public void setAudioQuality(int audioQuality) { 636 mAudioQuality = audioQuality; 637 updateCallCapabilities(); 638 } 639 640 private static Uri getHandleFromAddress(String address) { 641 // Address can be null for blocked calls. 642 if (address == null) { 643 address = ""; 644 } 645 return Uri.fromParts(TelephonyConnectionService.SCHEME_TEL, address, null); 646 } 647 648 /** 649 * Applies a capability to a capabilities bit-mask. 650 * 651 * @param capabilities The capabilities bit-mask. 652 * @param capability The capability to apply. 653 * @return The capabilities bit-mask with the capability applied. 654 */ 655 private int applyCapability(int capabilities, int capability) { 656 int newCapabilities = capabilities | capability; 657 return newCapabilities; 658 } 659 660 /** 661 * Removes a capability from a capabilities bit-mask. 662 * 663 * @param capabilities The capabilities bit-mask. 664 * @param capability The capability to remove. 665 * @return The capabilities bit-mask with the capability removed. 666 */ 667 private int removeCapability(int capabilities, int capability) { 668 int newCapabilities = capabilities & ~capability; 669 return newCapabilities; 670 } 671} 672