TelephonyConnection.java revision c869dd17deec2b09bfd028e3fb2c65887e322717
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 43 private final Handler mHandler = new Handler() { 44 @Override 45 public void handleMessage(Message msg) { 46 switch (msg.what) { 47 case MSG_PRECISE_CALL_STATE_CHANGED: 48 Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED"); 49 updateState(); 50 break; 51 case MSG_RINGBACK_TONE: 52 Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE"); 53 // TODO: This code assumes that there is only one connection in the foreground 54 // call, in other words, it punts on network-mediated conference calling. 55 if (getOriginalConnection() != getForegroundConnection()) { 56 Log.v(TelephonyConnection.this, "handleMessage, original connection is " + 57 "not foreground connection, skipping"); 58 return; 59 } 60 setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result); 61 break; 62 } 63 } 64 }; 65 66 private final PostDialListener mPostDialListener = new PostDialListener() { 67 @Override 68 public void onPostDialWait() { 69 Log.v(TelephonyConnection.this, "onPostDialWait"); 70 if (mOriginalConnection != null) { 71 setPostDialWait(mOriginalConnection.getRemainingPostDialString()); 72 } 73 } 74 }; 75 76 /** 77 * Listener for listening to events in the {@link com.android.internal.telephony.Connection}. 78 */ 79 private final com.android.internal.telephony.Connection.Listener mOriginalConnectionListener = 80 new com.android.internal.telephony.Connection.ListenerBase() { 81 @Override 82 public void onVideoStateChanged(int videoState) { 83 setVideoState(videoState); 84 } 85 86 /** 87 * The {@link com.android.internal.telephony.Connection} has reported a change in local 88 * video capability. 89 * 90 * @param capable True if capable. 91 */ 92 @Override 93 public void onLocalVideoCapabilityChanged(boolean capable) { 94 setLocalVideoCapable(capable); 95 } 96 97 /** 98 * The {@link com.android.internal.telephony.Connection} has reported a change in remote 99 * video capability. 100 * 101 * @param capable True if capable. 102 */ 103 @Override 104 public void onRemoteVideoCapabilityChanged(boolean capable) { 105 setRemoteVideoCapable(capable); 106 } 107 108 /** 109 * The {@link com.android.internal.telephony.Connection} has reported a change in the 110 * video call provider. 111 * 112 * @param videoProvider The video call provider. 113 */ 114 @Override 115 public void onVideoProviderChanged(VideoProvider videoProvider) { 116 setVideoProvider(videoProvider); 117 } 118 119 /** 120 * Used by the {@link com.android.internal.telephony.Connection} to report a change in the 121 * audio quality for the current call. 122 * 123 * @param audioQuality The audio quality. 124 */ 125 @Override 126 public void onAudioQualityChanged(int audioQuality) { 127 setAudioQuality(audioQuality); 128 } 129 }; 130 131 private com.android.internal.telephony.Connection mOriginalConnection; 132 private Call.State mOriginalConnectionState = Call.State.IDLE; 133 134 /** 135 * Determines if the {@link TelephonyConnection} has local video capabilities. 136 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 137 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 138 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 139 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 140 * layer. 141 */ 142 private boolean mLocalVideoCapable; 143 144 /** 145 * Determines if the {@link TelephonyConnection} has remote video capabilities. 146 * This is used when {@link TelephonyConnection#updateCallCapabilities()}} is called, 147 * ensuring the appropriate {@link PhoneCapabilities} are set. Since {@link PhoneCapabilities} 148 * can be rebuilt at any time it is necessary to track the video capabilities between rebuild. 149 * The {@link PhoneCapabilities} (including video capabilities) are communicated to the telecomm 150 * layer. 151 */ 152 private boolean mRemoteVideoCapable; 153 154 /** 155 * Determines the current audio quality for the {@link TelephonyConnection}. 156 * This is used when {@link TelephonyConnection#updateCallCapabilities}} is called to indicate 157 * whether a call has the {@link android.telecomm.CallCapabilities#VoLTE} capability. 158 */ 159 private int mAudioQuality; 160 161 protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) { 162 if (originalConnection != null) { 163 setOriginalConnection(originalConnection); 164 } 165 } 166 167 @Override 168 public void onSetAudioState(AudioState audioState) { 169 // TODO: update TTY mode. 170 if (getPhone() != null) { 171 getPhone().setEchoSuppressionEnabled(); 172 } 173 } 174 175 @Override 176 public void onSetState(int state) { 177 Log.v(this, "onSetState, state: " + Connection.stateToString(state)); 178 } 179 180 @Override 181 public void onDisconnect() { 182 Log.v(this, "onDisconnect"); 183 hangup(DisconnectCause.LOCAL); 184 } 185 186 @Override 187 public void onSeparate() { 188 Log.v(this, "onSeparate"); 189 if (mOriginalConnection != null) { 190 try { 191 mOriginalConnection.separate(); 192 } catch (CallStateException e) { 193 Log.e(this, e, "Call to Connection.separate failed with exception"); 194 } 195 } 196 } 197 198 @Override 199 public void onAbort() { 200 Log.v(this, "onAbort"); 201 hangup(DisconnectCause.LOCAL); 202 } 203 204 @Override 205 public void onHold() { 206 performHold(); 207 } 208 209 @Override 210 public void onUnhold() { 211 performUnhold(); 212 } 213 214 @Override 215 public void onAnswer(int videoState) { 216 Log.v(this, "onAnswer"); 217 // TODO: Tons of hairy logic is missing here around multiple active calls on 218 // CDMA devices. See {@link CallManager.acceptCall}. 219 220 if (isValidRingingCall() && getPhone() != null) { 221 try { 222 getPhone().acceptCall(videoState); 223 } catch (CallStateException e) { 224 Log.e(this, e, "Failed to accept call."); 225 } 226 } 227 } 228 229 @Override 230 public void onReject() { 231 Log.v(this, "onReject"); 232 if (isValidRingingCall()) { 233 hangup(DisconnectCause.INCOMING_REJECTED); 234 } 235 super.onReject(); 236 } 237 238 @Override 239 public void onPostDialContinue(boolean proceed) { 240 Log.v(this, "onPostDialContinue, proceed: " + proceed); 241 if (mOriginalConnection != null) { 242 if (proceed) { 243 mOriginalConnection.proceedAfterWaitChar(); 244 } else { 245 mOriginalConnection.cancelPostDial(); 246 } 247 } 248 } 249 250 @Override 251 public void onPhoneAccountClicked() { 252 Log.v(this, "onPhoneAccountClicked"); 253 } 254 255 public void performHold() { 256 Log.v(this, "performHold"); 257 // TODO: Can dialing calls be put on hold as well since they take up the 258 // foreground call slot? 259 if (Call.State.ACTIVE == mOriginalConnectionState) { 260 Log.v(this, "Holding active call"); 261 try { 262 Phone phone = mOriginalConnection.getCall().getPhone(); 263 Call ringingCall = phone.getRingingCall(); 264 265 // Although the method says switchHoldingAndActive, it eventually calls a RIL method 266 // called switchWaitingOrHoldingAndActive. What this means is that if we try to put 267 // a call on hold while a call-waiting call exists, it'll end up accepting the 268 // call-waiting call, which is bad if that was not the user's intention. We are 269 // cheating here and simply skipping it because we know any attempt to hold a call 270 // while a call-waiting call is happening is likely a request from Telecomm prior to 271 // accepting the call-waiting call. 272 // TODO: Investigate a better solution. It would be great here if we 273 // could "fake" hold by silencing the audio and microphone streams for this call 274 // instead of actually putting it on hold. 275 if (ringingCall.getState() != Call.State.WAITING) { 276 phone.switchHoldingAndActive(); 277 } 278 279 // TODO: Cdma calls are slightly different. 280 } catch (CallStateException e) { 281 Log.e(this, e, "Exception occurred while trying to put call on hold."); 282 } 283 } else { 284 Log.w(this, "Cannot put a call that is not currently active on hold."); 285 } 286 } 287 288 public void performUnhold() { 289 Log.v(this, "performUnhold"); 290 if (Call.State.HOLDING == mOriginalConnectionState) { 291 try { 292 // TODO: This doesn't handle multiple calls across connection services yet 293 mOriginalConnection.getCall().getPhone().switchHoldingAndActive(); 294 } catch (CallStateException e) { 295 Log.e(this, e, "Exception occurred while trying to release call from hold."); 296 } 297 } else { 298 Log.w(this, "Cannot release a call that is not already on hold from hold."); 299 } 300 } 301 302 public void performConference(TelephonyConnection otherConnection) {} 303 304 protected abstract int buildCallCapabilities(); 305 306 protected final void updateCallCapabilities() { 307 int newCallCapabilities = buildCallCapabilities(); 308 newCallCapabilities = applyVideoCapabilities(newCallCapabilities); 309 newCallCapabilities = applyAudioQualityCapabilities(newCallCapabilities); 310 311 if (getCallCapabilities() != newCallCapabilities) { 312 setCallCapabilities(newCallCapabilities); 313 } 314 } 315 316 protected final void updateHandle() { 317 updateCallCapabilities(); 318 if (mOriginalConnection != null) { 319 Uri handle = getHandleFromAddress(mOriginalConnection.getAddress()); 320 int presentation = mOriginalConnection.getNumberPresentation(); 321 if (!Objects.equals(handle, getHandle()) || 322 presentation != getHandlePresentation()) { 323 Log.v(this, "updateHandle, handle changed"); 324 setHandle(handle, presentation); 325 } 326 327 String name = mOriginalConnection.getCnapName(); 328 int namePresentation = mOriginalConnection.getCnapNamePresentation(); 329 if (!Objects.equals(name, getCallerDisplayName()) || 330 namePresentation != getCallerDisplayNamePresentation()) { 331 Log.v(this, "updateHandle, caller display name changed"); 332 setCallerDisplayName(name, namePresentation); 333 } 334 } 335 } 336 337 void onRemovedFromCallService() { 338 // Subclass can override this to do cleanup. 339 } 340 341 void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) { 342 Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection); 343 mOriginalConnection = originalConnection; 344 getPhone().registerForPreciseCallStateChanged( 345 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null); 346 getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null); 347 mOriginalConnection.addPostDialListener(mPostDialListener); 348 mOriginalConnection.addListener(mOriginalConnectionListener); 349 350 // Set video state and capabilities 351 setVideoState(mOriginalConnection.getVideoState()); 352 setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable()); 353 setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable()); 354 setVideoProvider(mOriginalConnection.getVideoProvider()); 355 setAudioQuality(mOriginalConnection.getAudioQuality()); 356 357 updateHandle(); 358 } 359 360 private void hangup(int disconnectCause) { 361 if (mOriginalConnection != null) { 362 try { 363 Call call = getCall(); 364 if (call != null) { 365 call.hangup(); 366 } else { 367 Log.w(this, "Attempting to hangup a connection without backing call."); 368 } 369 } catch (CallStateException e) { 370 Log.e(this, e, "Call to Connection.hangup failed with exception"); 371 } 372 } 373 374 // Set state deliberately since we are going to close() and will no longer be 375 // listening to state updates from mOriginalConnection 376 setDisconnected(disconnectCause, null); 377 close(); 378 } 379 380 com.android.internal.telephony.Connection getOriginalConnection() { 381 return mOriginalConnection; 382 } 383 384 protected Call getCall() { 385 if (mOriginalConnection != null) { 386 return mOriginalConnection.getCall(); 387 } 388 return null; 389 } 390 391 Phone getPhone() { 392 Call call = getCall(); 393 if (call != null) { 394 return call.getPhone(); 395 } 396 return null; 397 } 398 399 private com.android.internal.telephony.Connection getForegroundConnection() { 400 if (getPhone() != null) { 401 return getPhone().getForegroundCall().getEarliestConnection(); 402 } 403 return null; 404 } 405 406 /** 407 * Checks to see the original connection corresponds to an active incoming call. Returns false 408 * if there is no such actual call, or if the associated call is not incoming (See 409 * {@link Call.State#isRinging}). 410 */ 411 private boolean isValidRingingCall() { 412 if (getPhone() == null) { 413 Log.v(this, "isValidRingingCall, phone is null"); 414 return false; 415 } 416 417 Call ringingCall = getPhone().getRingingCall(); 418 if (!ringingCall.getState().isRinging()) { 419 Log.v(this, "isValidRingingCall, ringing call is not in ringing state"); 420 return false; 421 } 422 423 if (ringingCall.getEarliestConnection() != mOriginalConnection) { 424 Log.v(this, "isValidRingingCall, ringing call connection does not match"); 425 return false; 426 } 427 428 Log.v(this, "isValidRingingCall, returning true"); 429 return true; 430 } 431 432 private void updateState() { 433 if (mOriginalConnection == null) { 434 return; 435 } 436 437 Call.State newState = mOriginalConnection.getState(); 438 Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this); 439 if (mOriginalConnectionState != newState) { 440 mOriginalConnectionState = newState; 441 switch (newState) { 442 case IDLE: 443 break; 444 case ACTIVE: 445 setActive(); 446 break; 447 case HOLDING: 448 setOnHold(); 449 break; 450 case DIALING: 451 case ALERTING: 452 setDialing(); 453 break; 454 case INCOMING: 455 case WAITING: 456 setRinging(); 457 break; 458 case DISCONNECTED: 459 setDisconnected(mOriginalConnection.getDisconnectCause(), null); 460 close(); 461 break; 462 case DISCONNECTING: 463 break; 464 } 465 } 466 updateCallCapabilities(); 467 updateHandle(); 468 } 469 470 private void close() { 471 Log.v(this, "close"); 472 if (getPhone() != null) { 473 getPhone().unregisterForPreciseCallStateChanged(mHandler); 474 getPhone().unregisterForRingbackTone(mHandler); 475 } 476 mOriginalConnection = null; 477 destroy(); 478 } 479 480 /** 481 * Applies the video capability states to the CallCapabilities bit-mask. 482 * 483 * @param capabilities The CallCapabilities bit-mask. 484 * @return The capabilities with video capabilities applied. 485 */ 486 private int applyVideoCapabilities(int capabilities) { 487 int currentCapabilities = capabilities; 488 if (mRemoteVideoCapable) { 489 currentCapabilities = applyCapability(currentCapabilities, 490 PhoneCapabilities.SUPPORTS_VT_REMOTE); 491 } else { 492 currentCapabilities = removeCapability(currentCapabilities, 493 PhoneCapabilities.SUPPORTS_VT_REMOTE); 494 } 495 496 if (mLocalVideoCapable) { 497 currentCapabilities = applyCapability(currentCapabilities, 498 PhoneCapabilities.SUPPORTS_VT_LOCAL); 499 } else { 500 currentCapabilities = removeCapability(currentCapabilities, 501 PhoneCapabilities.SUPPORTS_VT_LOCAL); 502 } 503 return currentCapabilities; 504 } 505 506 /** 507 * Applies the audio capabilities to the {@code CallCapabilities} bit-mask. A call with high 508 * definition audio is considered to have the {@code VoLTE} call capability as VoLTE uses high 509 * definition audio. 510 * 511 * @param callCapabilities The {@code CallCapabilities} bit-mask. 512 * @return The capabilities with the audio capabilities applied. 513 */ 514 private int applyAudioQualityCapabilities(int callCapabilities) { 515 int currentCapabilities = callCapabilities; 516 517 if (mAudioQuality == 518 com.android.internal.telephony.Connection.AUDIO_QUALITY_HIGH_DEFINITION) { 519 currentCapabilities = applyCapability(currentCapabilities, PhoneCapabilities.VoLTE); 520 } else { 521 currentCapabilities = removeCapability(currentCapabilities, PhoneCapabilities.VoLTE); 522 } 523 524 return currentCapabilities; 525 } 526 527 /** 528 * Returns the local video capability state for the connection. 529 * 530 * @return {@code True} if the connection has local video capabilities. 531 */ 532 public boolean isLocalVideoCapable() { 533 return mLocalVideoCapable; 534 } 535 536 /** 537 * Returns the remote video capability state for the connection. 538 * 539 * @return {@code True} if the connection has remote video capabilities. 540 */ 541 public boolean isRemoteVideoCapable() { 542 return mRemoteVideoCapable; 543 } 544 545 /** 546 * Sets whether video capability is present locally. Used during rebuild of the 547 * {@link PhoneCapabilities} to set the video call capabilities. 548 * 549 * @param capable {@code True} if video capable. 550 */ 551 public void setLocalVideoCapable(boolean capable) { 552 mLocalVideoCapable = capable; 553 updateCallCapabilities(); 554 } 555 556 /** 557 * Sets whether video capability is present remotely. Used during rebuild of the 558 * {@link PhoneCapabilities} to set the video call capabilities. 559 * 560 * @param capable {@code True} if video capable. 561 */ 562 public void setRemoteVideoCapable(boolean capable) { 563 mRemoteVideoCapable = capable; 564 updateCallCapabilities(); 565 } 566 567 /** 568 * Sets the current call audio quality. Used during rebuild of the 569 * {@link PhoneCapabilities} to set or unset the {@link PhoneCapabilities#VoLTE} capability. 570 * 571 * @param audioQuality The audio quality. 572 */ 573 public void setAudioQuality(int audioQuality) { 574 mAudioQuality = audioQuality; 575 updateCallCapabilities(); 576 } 577 578 private static Uri getHandleFromAddress(String address) { 579 // Address can be null for blocked calls. 580 if (address == null) { 581 address = ""; 582 } 583 return Uri.fromParts(TelephonyConnectionService.SCHEME_TEL, address, null); 584 } 585 586 /** 587 * Applies a capability to a capabilities bit-mask. 588 * 589 * @param capabilities The capabilities bit-mask. 590 * @param capability The capability to apply. 591 * @return The capabilities bit-mask with the capability applied. 592 */ 593 private int applyCapability(int capabilities, int capability) { 594 int newCapabilities = capabilities | capability; 595 return newCapabilities; 596 } 597 598 /** 599 * Removes a capability from a capabilities bit-mask. 600 * 601 * @param capabilities The capabilities bit-mask. 602 * @param capability The capability to remove. 603 * @return The capabilities bit-mask with the capability removed. 604 */ 605 private int removeCapability(int capabilities, int capability) { 606 int newCapabilities = capabilities & ~capability; 607 return newCapabilities; 608 } 609} 610