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