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