1 /* 2 * Copyright (C) 2010 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.internal.telephony.sip; 18 19 import android.content.Context; 20 import android.media.AudioManager; 21 import android.net.rtp.AudioGroup; 22 import android.net.sip.SipAudioCall; 23 import android.net.sip.SipErrorCode; 24 import android.net.sip.SipException; 25 import android.net.sip.SipManager; 26 import android.net.sip.SipProfile; 27 import android.net.sip.SipSession; 28 import android.os.AsyncResult; 29 import android.os.Bundle; 30 import android.os.Message; 31 import android.telephony.DisconnectCause; 32 import android.telephony.PhoneNumberUtils; 33 import android.telephony.ServiceState; 34 import android.text.TextUtils; 35 import android.telephony.Rlog; 36 37 import com.android.internal.telephony.Call; 38 import com.android.internal.telephony.CallStateException; 39 import com.android.internal.telephony.Connection; 40 import com.android.internal.telephony.Phone; 41 import com.android.internal.telephony.PhoneConstants; 42 import com.android.internal.telephony.PhoneNotifier; 43 44 import java.text.ParseException; 45 import java.util.List; 46 import java.util.regex.Pattern; 47 48 /** 49 * {@hide} 50 */ 51 public class SipPhone extends SipPhoneBase { 52 private static final String LOG_TAG = "SipPhone"; 53 private static final boolean DBG = true; 54 private static final boolean VDBG = false; // STOPSHIP if true 55 private static final int TIMEOUT_MAKE_CALL = 15; // in seconds 56 private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds 57 private static final int TIMEOUT_HOLD_CALL = 15; // in seconds 58 59 // A call that is ringing or (call) waiting 60 private SipCall mRingingCall = new SipCall(); 61 private SipCall mForegroundCall = new SipCall(); 62 private SipCall mBackgroundCall = new SipCall(); 63 64 private SipManager mSipManager; 65 private SipProfile mProfile; 66 67 SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) { 68 super("SIP:" + profile.getUriString(), context, notifier); 69 70 if (DBG) log("new SipPhone: " + profile.getUriString()); 71 mRingingCall = new SipCall(); 72 mForegroundCall = new SipCall(); 73 mBackgroundCall = new SipCall(); 74 mProfile = profile; 75 mSipManager = SipManager.newInstance(context); 76 } 77 78 @Override 79 public boolean equals(Object o) { 80 if (o == this) return true; 81 if (!(o instanceof SipPhone)) return false; 82 SipPhone that = (SipPhone) o; 83 return mProfile.getUriString().equals(that.mProfile.getUriString()); 84 } 85 86 public String getSipUri() { 87 return mProfile.getUriString(); 88 } 89 90 public boolean equals(SipPhone phone) { 91 return getSipUri().equals(phone.getSipUri()); 92 } 93 94 public Connection takeIncomingCall(Object incomingCall) { 95 // FIXME: Is synchronizing on the class necessary, should we use a mLockObj? 96 // Also there are many things not synchronized, of course 97 // this may be true of CdmaPhone and GsmPhone too!!! 98 synchronized (SipPhone.class) { 99 if (!(incomingCall instanceof SipAudioCall)) { 100 if (DBG) log("takeIncomingCall: ret=null, not a SipAudioCall"); 101 return null; 102 } 103 if (mRingingCall.getState().isAlive()) { 104 if (DBG) log("takeIncomingCall: ret=null, ringingCall not alive"); 105 return null; 106 } 107 108 // FIXME: is it true that we cannot take any incoming call if 109 // both foreground and background are active 110 if (mForegroundCall.getState().isAlive() 111 && mBackgroundCall.getState().isAlive()) { 112 if (DBG) { 113 log("takeIncomingCall: ret=null," + " foreground and background both alive"); 114 } 115 return null; 116 } 117 118 try { 119 SipAudioCall sipAudioCall = (SipAudioCall) incomingCall; 120 if (DBG) log("takeIncomingCall: taking call from: " 121 + sipAudioCall.getPeerProfile().getUriString()); 122 String localUri = sipAudioCall.getLocalProfile().getUriString(); 123 if (localUri.equals(mProfile.getUriString())) { 124 boolean makeCallWait = mForegroundCall.getState().isAlive(); 125 SipConnection connection = mRingingCall.initIncomingCall(sipAudioCall, 126 makeCallWait); 127 if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) { 128 // Peer cancelled the call! 129 if (DBG) log(" takeIncomingCall: call cancelled !!"); 130 mRingingCall.reset(); 131 connection = null; 132 } 133 return connection; 134 } 135 } catch (Exception e) { 136 // Peer may cancel the call at any time during the time we hook 137 // up ringingCall with sipAudioCall. Clean up ringingCall when 138 // that happens. 139 if (DBG) log(" takeIncomingCall: exception e=" + e); 140 mRingingCall.reset(); 141 } 142 if (DBG) log("takeIncomingCall: NOT taking !!"); 143 return null; 144 } 145 } 146 147 @Override 148 public void acceptCall(int videoState) throws CallStateException { 149 synchronized (SipPhone.class) { 150 if ((mRingingCall.getState() == Call.State.INCOMING) || 151 (mRingingCall.getState() == Call.State.WAITING)) { 152 if (DBG) log("acceptCall: accepting"); 153 // Always unmute when answering a new call 154 mRingingCall.setMute(false); 155 mRingingCall.acceptCall(); 156 } else { 157 if (DBG) { 158 log("acceptCall:" + 159 " throw CallStateException(\"phone not ringing\")"); 160 } 161 throw new CallStateException("phone not ringing"); 162 } 163 } 164 } 165 166 @Override 167 public void rejectCall() throws CallStateException { 168 synchronized (SipPhone.class) { 169 if (mRingingCall.getState().isRinging()) { 170 if (DBG) log("rejectCall: rejecting"); 171 mRingingCall.rejectCall(); 172 } else { 173 if (DBG) { 174 log("rejectCall:" + 175 " throw CallStateException(\"phone not ringing\")"); 176 } 177 throw new CallStateException("phone not ringing"); 178 } 179 } 180 } 181 182 @Override 183 public Connection dial(String dialString, int videoState) throws CallStateException { 184 synchronized (SipPhone.class) { 185 return dialInternal(dialString, videoState); 186 } 187 } 188 189 private Connection dialInternal(String dialString, int videoState) 190 throws CallStateException { 191 if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx")); 192 clearDisconnected(); 193 194 if (!canDial()) { 195 throw new CallStateException("dialInternal: cannot dial in current state"); 196 } 197 if (mForegroundCall.getState() == SipCall.State.ACTIVE) { 198 switchHoldingAndActive(); 199 } 200 if (mForegroundCall.getState() != SipCall.State.IDLE) { 201 //we should have failed in !canDial() above before we get here 202 throw new CallStateException("cannot dial in current state"); 203 } 204 205 mForegroundCall.setMute(false); 206 try { 207 Connection c = mForegroundCall.dial(dialString); 208 return c; 209 } catch (SipException e) { 210 loge("dialInternal: ", e); 211 throw new CallStateException("dial error: " + e); 212 } 213 } 214 215 @Override 216 public void switchHoldingAndActive() throws CallStateException { 217 if (DBG) log("switchHoldingAndActive: switch fg and bg"); 218 synchronized (SipPhone.class) { 219 mForegroundCall.switchWith(mBackgroundCall); 220 if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold(); 221 if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold(); 222 } 223 } 224 225 @Override 226 public boolean canConference() { 227 if (DBG) log("canConference: ret=true"); 228 return true; 229 } 230 231 @Override 232 public void conference() throws CallStateException { 233 synchronized (SipPhone.class) { 234 if ((mForegroundCall.getState() != SipCall.State.ACTIVE) 235 || (mForegroundCall.getState() != SipCall.State.ACTIVE)) { 236 throw new CallStateException("wrong state to merge calls: fg=" 237 + mForegroundCall.getState() + ", bg=" 238 + mBackgroundCall.getState()); 239 } 240 if (DBG) log("conference: merge fg & bg"); 241 mForegroundCall.merge(mBackgroundCall); 242 } 243 } 244 245 public void conference(Call that) throws CallStateException { 246 synchronized (SipPhone.class) { 247 if (!(that instanceof SipCall)) { 248 throw new CallStateException("expect " + SipCall.class 249 + ", cannot merge with " + that.getClass()); 250 } 251 mForegroundCall.merge((SipCall) that); 252 } 253 } 254 255 @Override 256 public boolean canTransfer() { 257 return false; 258 } 259 260 @Override 261 public void explicitCallTransfer() { 262 //mCT.explicitCallTransfer(); 263 } 264 265 @Override 266 public void clearDisconnected() { 267 synchronized (SipPhone.class) { 268 mRingingCall.clearDisconnected(); 269 mForegroundCall.clearDisconnected(); 270 mBackgroundCall.clearDisconnected(); 271 272 updatePhoneState(); 273 notifyPreciseCallStateChanged(); 274 } 275 } 276 277 @Override 278 public void sendDtmf(char c) { 279 if (!PhoneNumberUtils.is12Key(c)) { 280 loge("sendDtmf called with invalid character '" + c + "'"); 281 } else if (mForegroundCall.getState().isAlive()) { 282 synchronized (SipPhone.class) { 283 mForegroundCall.sendDtmf(c); 284 } 285 } 286 } 287 288 @Override 289 public void startDtmf(char c) { 290 if (!PhoneNumberUtils.is12Key(c)) { 291 loge("startDtmf called with invalid character '" + c + "'"); 292 } else { 293 sendDtmf(c); 294 } 295 } 296 297 @Override 298 public void stopDtmf() { 299 // no op 300 } 301 302 public void sendBurstDtmf(String dtmfString) { 303 loge("sendBurstDtmf() is a CDMA method"); 304 } 305 306 @Override 307 public void getOutgoingCallerIdDisplay(Message onComplete) { 308 // FIXME: what to reply? 309 AsyncResult.forMessage(onComplete, null, null); 310 onComplete.sendToTarget(); 311 } 312 313 @Override 314 public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, 315 Message onComplete) { 316 // FIXME: what's this for SIP? 317 AsyncResult.forMessage(onComplete, null, null); 318 onComplete.sendToTarget(); 319 } 320 321 @Override 322 public void getCallWaiting(Message onComplete) { 323 // FIXME: what to reply? 324 AsyncResult.forMessage(onComplete, null, null); 325 onComplete.sendToTarget(); 326 } 327 328 @Override 329 public void setCallWaiting(boolean enable, Message onComplete) { 330 // FIXME: what to reply? 331 loge("call waiting not supported"); 332 } 333 334 @Override 335 public void setEchoSuppressionEnabled() { 336 // Echo suppression may not be available on every device. So, check 337 // whether it is supported 338 synchronized (SipPhone.class) { 339 AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 340 String echoSuppression = audioManager.getParameters("ec_supported"); 341 if (echoSuppression.contains("off")) { 342 mForegroundCall.setAudioGroupMode(); 343 } 344 } 345 } 346 347 @Override 348 public void setMute(boolean muted) { 349 synchronized (SipPhone.class) { 350 mForegroundCall.setMute(muted); 351 } 352 } 353 354 @Override 355 public boolean getMute() { 356 return (mForegroundCall.getState().isAlive() 357 ? mForegroundCall.getMute() 358 : mBackgroundCall.getMute()); 359 } 360 361 @Override 362 public Call getForegroundCall() { 363 return mForegroundCall; 364 } 365 366 @Override 367 public Call getBackgroundCall() { 368 return mBackgroundCall; 369 } 370 371 @Override 372 public Call getRingingCall() { 373 return mRingingCall; 374 } 375 376 @Override 377 public ServiceState getServiceState() { 378 // FIXME: we may need to provide this when data connectivity is lost 379 // or when server is down 380 return super.getServiceState(); 381 } 382 383 private String getUriString(SipProfile p) { 384 // SipProfile.getUriString() may contain "SIP:" and port 385 return p.getUserName() + "@" + getSipDomain(p); 386 } 387 388 private String getSipDomain(SipProfile p) { 389 String domain = p.getSipDomain(); 390 // TODO: move this to SipProfile 391 if (domain.endsWith(":5060")) { 392 return domain.substring(0, domain.length() - 5); 393 } else { 394 return domain; 395 } 396 } 397 398 private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) { 399 if (sipAudioCall.isOnHold()) return Call.State.HOLDING; 400 int sessionState = sipAudioCall.getState(); 401 switch (sessionState) { 402 case SipSession.State.READY_TO_CALL: return Call.State.IDLE; 403 case SipSession.State.INCOMING_CALL: 404 case SipSession.State.INCOMING_CALL_ANSWERING: return Call.State.INCOMING; 405 case SipSession.State.OUTGOING_CALL: return Call.State.DIALING; 406 case SipSession.State.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING; 407 case SipSession.State.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING; 408 case SipSession.State.IN_CALL: return Call.State.ACTIVE; 409 default: 410 slog("illegal connection state: " + sessionState); 411 return Call.State.DISCONNECTED; 412 } 413 } 414 415 private void log(String s) { 416 Rlog.d(LOG_TAG, s); 417 } 418 419 private static void slog(String s) { 420 Rlog.d(LOG_TAG, s); 421 } 422 423 private void loge(String s) { 424 Rlog.e(LOG_TAG, s); 425 } 426 427 private void loge(String s, Exception e) { 428 Rlog.e(LOG_TAG, s, e); 429 } 430 431 private class SipCall extends SipCallBase { 432 private static final String SC_TAG = "SipCall"; 433 private static final boolean SC_DBG = true; 434 private static final boolean SC_VDBG = false; // STOPSHIP if true 435 436 void reset() { 437 if (SC_DBG) log("reset"); 438 mConnections.clear(); 439 setState(Call.State.IDLE); 440 } 441 442 void switchWith(SipCall that) { 443 if (SC_DBG) log("switchWith"); 444 synchronized (SipPhone.class) { 445 SipCall tmp = new SipCall(); 446 tmp.takeOver(this); 447 this.takeOver(that); 448 that.takeOver(tmp); 449 } 450 } 451 452 private void takeOver(SipCall that) { 453 if (SC_DBG) log("takeOver"); 454 mConnections = that.mConnections; 455 mState = that.mState; 456 for (Connection c : mConnections) { 457 ((SipConnection) c).changeOwner(this); 458 } 459 } 460 461 @Override 462 public Phone getPhone() { 463 return SipPhone.this; 464 } 465 466 @Override 467 public List<Connection> getConnections() { 468 if (SC_VDBG) log("getConnections"); 469 synchronized (SipPhone.class) { 470 // FIXME should return Collections.unmodifiableList(); 471 return mConnections; 472 } 473 } 474 475 Connection dial(String originalNumber) throws SipException { 476 if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx")); 477 // TODO: Should this be synchronized? 478 String calleeSipUri = originalNumber; 479 if (!calleeSipUri.contains("@")) { 480 String replaceStr = Pattern.quote(mProfile.getUserName() + "@"); 481 calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr, 482 calleeSipUri + "@"); 483 } 484 try { 485 SipProfile callee = 486 new SipProfile.Builder(calleeSipUri).build(); 487 SipConnection c = new SipConnection(this, callee, 488 originalNumber); 489 c.dial(); 490 mConnections.add(c); 491 setState(Call.State.DIALING); 492 return c; 493 } catch (ParseException e) { 494 throw new SipException("dial", e); 495 } 496 } 497 498 @Override 499 public void hangup() throws CallStateException { 500 synchronized (SipPhone.class) { 501 if (mState.isAlive()) { 502 if (SC_DBG) log("hangup: call " + getState() 503 + ": " + this + " on phone " + getPhone()); 504 setState(State.DISCONNECTING); 505 CallStateException excp = null; 506 for (Connection c : mConnections) { 507 try { 508 c.hangup(); 509 } catch (CallStateException e) { 510 excp = e; 511 } 512 } 513 if (excp != null) throw excp; 514 } else { 515 if (SC_DBG) log("hangup: dead call " + getState() 516 + ": " + this + " on phone " + getPhone()); 517 } 518 } 519 } 520 521 SipConnection initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) { 522 SipProfile callee = sipAudioCall.getPeerProfile(); 523 SipConnection c = new SipConnection(this, callee); 524 mConnections.add(c); 525 526 Call.State newState = makeCallWait ? State.WAITING : State.INCOMING; 527 c.initIncomingCall(sipAudioCall, newState); 528 529 setState(newState); 530 notifyNewRingingConnectionP(c); 531 return c; 532 } 533 534 void rejectCall() throws CallStateException { 535 if (SC_DBG) log("rejectCall:"); 536 hangup(); 537 } 538 539 void acceptCall() throws CallStateException { 540 if (SC_DBG) log("acceptCall: accepting"); 541 if (this != mRingingCall) { 542 throw new CallStateException("acceptCall() in a non-ringing call"); 543 } 544 if (mConnections.size() != 1) { 545 throw new CallStateException("acceptCall() in a conf call"); 546 } 547 ((SipConnection) mConnections.get(0)).acceptCall(); 548 } 549 550 private boolean isSpeakerOn() { 551 Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) 552 .isSpeakerphoneOn(); 553 if (SC_VDBG) log("isSpeakerOn: ret=" + ret); 554 return ret; 555 } 556 557 void setAudioGroupMode() { 558 AudioGroup audioGroup = getAudioGroup(); 559 if (audioGroup == null) { 560 if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore"); 561 return; 562 } 563 int mode = audioGroup.getMode(); 564 if (mState == State.HOLDING) { 565 audioGroup.setMode(AudioGroup.MODE_ON_HOLD); 566 } else if (getMute()) { 567 audioGroup.setMode(AudioGroup.MODE_MUTED); 568 } else if (isSpeakerOn()) { 569 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); 570 } else { 571 audioGroup.setMode(AudioGroup.MODE_NORMAL); 572 } 573 if (SC_DBG) log(String.format( 574 "setAudioGroupMode change: %d --> %d", mode, 575 audioGroup.getMode())); 576 } 577 578 void hold() throws CallStateException { 579 if (SC_DBG) log("hold:"); 580 setState(State.HOLDING); 581 for (Connection c : mConnections) ((SipConnection) c).hold(); 582 setAudioGroupMode(); 583 } 584 585 void unhold() throws CallStateException { 586 if (SC_DBG) log("unhold:"); 587 setState(State.ACTIVE); 588 AudioGroup audioGroup = new AudioGroup(); 589 for (Connection c : mConnections) { 590 ((SipConnection) c).unhold(audioGroup); 591 } 592 setAudioGroupMode(); 593 } 594 595 void setMute(boolean muted) { 596 if (SC_DBG) log("setMute: muted=" + muted); 597 for (Connection c : mConnections) { 598 ((SipConnection) c).setMute(muted); 599 } 600 } 601 602 boolean getMute() { 603 boolean ret = mConnections.isEmpty() 604 ? false 605 : ((SipConnection) mConnections.get(0)).getMute(); 606 if (SC_DBG) log("getMute: ret=" + ret); 607 return ret; 608 } 609 610 void merge(SipCall that) throws CallStateException { 611 if (SC_DBG) log("merge:"); 612 AudioGroup audioGroup = getAudioGroup(); 613 614 // copy to an array to avoid concurrent modification as connections 615 // in that.connections will be removed in add(SipConnection). 616 Connection[] cc = that.mConnections.toArray( 617 new Connection[that.mConnections.size()]); 618 for (Connection c : cc) { 619 SipConnection conn = (SipConnection) c; 620 add(conn); 621 if (conn.getState() == Call.State.HOLDING) { 622 conn.unhold(audioGroup); 623 } 624 } 625 that.setState(Call.State.IDLE); 626 } 627 628 private void add(SipConnection conn) { 629 if (SC_DBG) log("add:"); 630 SipCall call = conn.getCall(); 631 if (call == this) return; 632 if (call != null) call.mConnections.remove(conn); 633 634 mConnections.add(conn); 635 conn.changeOwner(this); 636 } 637 638 void sendDtmf(char c) { 639 if (SC_DBG) log("sendDtmf: c=" + c); 640 AudioGroup audioGroup = getAudioGroup(); 641 if (audioGroup == null) { 642 if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c); 643 return; 644 } 645 audioGroup.sendDtmf(convertDtmf(c)); 646 } 647 648 private int convertDtmf(char c) { 649 int code = c - '0'; 650 if ((code < 0) || (code > 9)) { 651 switch (c) { 652 case '*': return 10; 653 case '#': return 11; 654 case 'A': return 12; 655 case 'B': return 13; 656 case 'C': return 14; 657 case 'D': return 15; 658 default: 659 throw new IllegalArgumentException( 660 "invalid DTMF char: " + (int) c); 661 } 662 } 663 return code; 664 } 665 666 @Override 667 protected void setState(State newState) { 668 if (mState != newState) { 669 if (SC_DBG) log("setState: cur state" + mState 670 + " --> " + newState + ": " + this + ": on phone " 671 + getPhone() + " " + mConnections.size()); 672 673 if (newState == Call.State.ALERTING) { 674 mState = newState; // need in ALERTING to enable ringback 675 startRingbackTone(); 676 } else if (mState == Call.State.ALERTING) { 677 stopRingbackTone(); 678 } 679 mState = newState; 680 updatePhoneState(); 681 notifyPreciseCallStateChanged(); 682 } 683 } 684 685 void onConnectionStateChanged(SipConnection conn) { 686 // this can be called back when a conf call is formed 687 if (SC_DBG) log("onConnectionStateChanged: conn=" + conn); 688 if (mState != State.ACTIVE) { 689 setState(conn.getState()); 690 } 691 } 692 693 void onConnectionEnded(SipConnection conn) { 694 // set state to DISCONNECTED only when all conns are disconnected 695 if (SC_DBG) log("onConnectionEnded: conn=" + conn); 696 if (mState != State.DISCONNECTED) { 697 boolean allConnectionsDisconnected = true; 698 if (SC_DBG) log("---check connections: " 699 + mConnections.size()); 700 for (Connection c : mConnections) { 701 if (SC_DBG) log(" state=" + c.getState() + ": " 702 + c); 703 if (c.getState() != State.DISCONNECTED) { 704 allConnectionsDisconnected = false; 705 break; 706 } 707 } 708 if (allConnectionsDisconnected) setState(State.DISCONNECTED); 709 } 710 notifyDisconnectP(conn); 711 } 712 713 private AudioGroup getAudioGroup() { 714 if (mConnections.isEmpty()) return null; 715 return ((SipConnection) mConnections.get(0)).getAudioGroup(); 716 } 717 718 private void log(String s) { 719 Rlog.d(SC_TAG, s); 720 } 721 } 722 723 private class SipConnection extends SipConnectionBase { 724 private static final String SCN_TAG = "SipConnection"; 725 private static final boolean SCN_DBG = true; 726 727 private SipCall mOwner; 728 private SipAudioCall mSipAudioCall; 729 private Call.State mState = Call.State.IDLE; 730 private SipProfile mPeer; 731 private boolean mIncoming = false; 732 private String mOriginalNumber; // may be a PSTN number 733 734 private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() { 735 @Override 736 protected void onCallEnded(int cause) { 737 if (getDisconnectCause() != DisconnectCause.LOCAL) { 738 setDisconnectCause(cause); 739 } 740 synchronized (SipPhone.class) { 741 setState(Call.State.DISCONNECTED); 742 SipAudioCall sipAudioCall = mSipAudioCall; 743 // FIXME: This goes null and is synchronized, but many uses aren't sync'd 744 mSipAudioCall = null; 745 String sessionState = (sipAudioCall == null) 746 ? "" 747 : (sipAudioCall.getState() + ", "); 748 if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: " 749 + mPeer.getUriString() + ": " + sessionState 750 + "cause: " + getDisconnectCause() + ", on phone " 751 + getPhone()); 752 if (sipAudioCall != null) { 753 sipAudioCall.setListener(null); 754 sipAudioCall.close(); 755 } 756 mOwner.onConnectionEnded(SipConnection.this); 757 } 758 } 759 760 @Override 761 public void onCallEstablished(SipAudioCall call) { 762 onChanged(call); 763 // Race onChanged synchronized this isn't 764 if (mState == Call.State.ACTIVE) call.startAudio(); 765 } 766 767 @Override 768 public void onCallHeld(SipAudioCall call) { 769 onChanged(call); 770 // Race onChanged synchronized this isn't 771 if (mState == Call.State.HOLDING) call.startAudio(); 772 } 773 774 @Override 775 public void onChanged(SipAudioCall call) { 776 synchronized (SipPhone.class) { 777 Call.State newState = getCallStateFrom(call); 778 if (mState == newState) return; 779 if (newState == Call.State.INCOMING) { 780 setState(mOwner.getState()); // INCOMING or WAITING 781 } else { 782 if (mOwner == mRingingCall) { 783 if (mRingingCall.getState() == Call.State.WAITING) { 784 try { 785 switchHoldingAndActive(); 786 } catch (CallStateException e) { 787 // disconnect the call. 788 onCallEnded(DisconnectCause.LOCAL); 789 return; 790 } 791 } 792 mForegroundCall.switchWith(mRingingCall); 793 } 794 setState(newState); 795 } 796 mOwner.onConnectionStateChanged(SipConnection.this); 797 if (SCN_DBG) log("onChanged: " 798 + mPeer.getUriString() + ": " + mState 799 + " on phone " + getPhone()); 800 } 801 } 802 803 @Override 804 protected void onError(int cause) { 805 if (SCN_DBG) log("onError: " + cause); 806 onCallEnded(cause); 807 } 808 }; 809 810 public SipConnection(SipCall owner, SipProfile callee, 811 String originalNumber) { 812 super(originalNumber); 813 mOwner = owner; 814 mPeer = callee; 815 mOriginalNumber = originalNumber; 816 } 817 818 public SipConnection(SipCall owner, SipProfile callee) { 819 this(owner, callee, getUriString(callee)); 820 } 821 822 @Override 823 public String getCnapName() { 824 String displayName = mPeer.getDisplayName(); 825 return TextUtils.isEmpty(displayName) ? null 826 : displayName; 827 } 828 829 @Override 830 public int getNumberPresentation() { 831 return PhoneConstants.PRESENTATION_ALLOWED; 832 } 833 834 void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) { 835 setState(newState); 836 mSipAudioCall = sipAudioCall; 837 sipAudioCall.setListener(mAdapter); // call back to set state 838 mIncoming = true; 839 } 840 841 void acceptCall() throws CallStateException { 842 try { 843 mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL); 844 } catch (SipException e) { 845 throw new CallStateException("acceptCall(): " + e); 846 } 847 } 848 849 void changeOwner(SipCall owner) { 850 mOwner = owner; 851 } 852 853 AudioGroup getAudioGroup() { 854 if (mSipAudioCall == null) return null; 855 return mSipAudioCall.getAudioGroup(); 856 } 857 858 void dial() throws SipException { 859 setState(Call.State.DIALING); 860 mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null, 861 TIMEOUT_MAKE_CALL); 862 mSipAudioCall.setListener(mAdapter); 863 } 864 865 void hold() throws CallStateException { 866 setState(Call.State.HOLDING); 867 try { 868 mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL); 869 } catch (SipException e) { 870 throw new CallStateException("hold(): " + e); 871 } 872 } 873 874 void unhold(AudioGroup audioGroup) throws CallStateException { 875 mSipAudioCall.setAudioGroup(audioGroup); 876 setState(Call.State.ACTIVE); 877 try { 878 mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL); 879 } catch (SipException e) { 880 throw new CallStateException("unhold(): " + e); 881 } 882 } 883 884 void setMute(boolean muted) { 885 if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) { 886 if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted); 887 mSipAudioCall.toggleMute(); 888 } 889 } 890 891 boolean getMute() { 892 return (mSipAudioCall == null) ? false 893 : mSipAudioCall.isMuted(); 894 } 895 896 @Override 897 protected void setState(Call.State state) { 898 if (state == mState) return; 899 super.setState(state); 900 mState = state; 901 } 902 903 @Override 904 public Call.State getState() { 905 return mState; 906 } 907 908 @Override 909 public boolean isIncoming() { 910 return mIncoming; 911 } 912 913 @Override 914 public String getAddress() { 915 // Phone app uses this to query caller ID. Return the original dial 916 // number (which may be a PSTN number) instead of the peer's SIP 917 // URI. 918 return mOriginalNumber; 919 } 920 921 @Override 922 public SipCall getCall() { 923 return mOwner; 924 } 925 926 @Override 927 protected Phone getPhone() { 928 return mOwner.getPhone(); 929 } 930 931 @Override 932 public void hangup() throws CallStateException { 933 synchronized (SipPhone.class) { 934 if (SCN_DBG) log("hangup: conn=" + mPeer.getUriString() 935 + ": " + mState + ": on phone " 936 + getPhone().getPhoneName()); 937 if (!mState.isAlive()) return; 938 try { 939 SipAudioCall sipAudioCall = mSipAudioCall; 940 if (sipAudioCall != null) { 941 sipAudioCall.setListener(null); 942 sipAudioCall.endCall(); 943 } 944 } catch (SipException e) { 945 throw new CallStateException("hangup(): " + e); 946 } finally { 947 mAdapter.onCallEnded(((mState == Call.State.INCOMING) 948 || (mState == Call.State.WAITING)) 949 ? DisconnectCause.INCOMING_REJECTED 950 : DisconnectCause.LOCAL); 951 } 952 } 953 } 954 955 @Override 956 public void separate() throws CallStateException { 957 synchronized (SipPhone.class) { 958 SipCall call = (getPhone() == SipPhone.this) 959 ? (SipCall) getBackgroundCall() 960 : (SipCall) getForegroundCall(); 961 if (call.getState() != Call.State.IDLE) { 962 throw new CallStateException( 963 "cannot put conn back to a call in non-idle state: " 964 + call.getState()); 965 } 966 if (SCN_DBG) log("separate: conn=" 967 + mPeer.getUriString() + " from " + mOwner + " back to " 968 + call); 969 970 // separate the AudioGroup and connection from the original call 971 Phone originalPhone = getPhone(); 972 AudioGroup audioGroup = call.getAudioGroup(); // may be null 973 call.add(this); 974 mSipAudioCall.setAudioGroup(audioGroup); 975 976 // put the original call to bg; and the separated call becomes 977 // fg if it was in bg 978 originalPhone.switchHoldingAndActive(); 979 980 // start audio and notify the phone app of the state change 981 call = (SipCall) getForegroundCall(); 982 mSipAudioCall.startAudio(); 983 call.onConnectionStateChanged(this); 984 } 985 } 986 987 private void log(String s) { 988 Rlog.d(SCN_TAG, s); 989 } 990 } 991 992 private abstract class SipAudioCallAdapter extends SipAudioCall.Listener { 993 private static final String SACA_TAG = "SipAudioCallAdapter"; 994 private static final boolean SACA_DBG = true; 995 /** Call ended with cause defined in {@link DisconnectCause}. */ 996 protected abstract void onCallEnded(int cause); 997 /** Call failed with cause defined in {@link DisconnectCause}. */ 998 protected abstract void onError(int cause); 999 1000 @Override 1001 public void onCallEnded(SipAudioCall call) { 1002 if (SACA_DBG) log("onCallEnded: call=" + call); 1003 onCallEnded(call.isInCall() 1004 ? DisconnectCause.NORMAL 1005 : DisconnectCause.INCOMING_MISSED); 1006 } 1007 1008 @Override 1009 public void onCallBusy(SipAudioCall call) { 1010 if (SACA_DBG) log("onCallBusy: call=" + call); 1011 onCallEnded(DisconnectCause.BUSY); 1012 } 1013 1014 @Override 1015 public void onError(SipAudioCall call, int errorCode, 1016 String errorMessage) { 1017 if (SACA_DBG) { 1018 log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode) 1019 + ": " + errorMessage); 1020 } 1021 switch (errorCode) { 1022 case SipErrorCode.SERVER_UNREACHABLE: 1023 onError(DisconnectCause.SERVER_UNREACHABLE); 1024 break; 1025 case SipErrorCode.PEER_NOT_REACHABLE: 1026 onError(DisconnectCause.NUMBER_UNREACHABLE); 1027 break; 1028 case SipErrorCode.INVALID_REMOTE_URI: 1029 onError(DisconnectCause.INVALID_NUMBER); 1030 break; 1031 case SipErrorCode.TIME_OUT: 1032 case SipErrorCode.TRANSACTION_TERMINTED: 1033 onError(DisconnectCause.TIMED_OUT); 1034 break; 1035 case SipErrorCode.DATA_CONNECTION_LOST: 1036 onError(DisconnectCause.LOST_SIGNAL); 1037 break; 1038 case SipErrorCode.INVALID_CREDENTIALS: 1039 onError(DisconnectCause.INVALID_CREDENTIALS); 1040 break; 1041 case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION: 1042 onError(DisconnectCause.OUT_OF_NETWORK); 1043 break; 1044 case SipErrorCode.SERVER_ERROR: 1045 onError(DisconnectCause.SERVER_ERROR); 1046 break; 1047 case SipErrorCode.SOCKET_ERROR: 1048 case SipErrorCode.CLIENT_ERROR: 1049 default: 1050 onError(DisconnectCause.ERROR_UNSPECIFIED); 1051 } 1052 } 1053 1054 private void log(String s) { 1055 Rlog.d(SACA_TAG, s); 1056 } 1057 } 1058 } 1059