gt:         return p.getUserName() + "@" + getSipDomain(p);
context: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.telephony.sip;
  
  import android.content.Context;
  import android.media.AudioManager;
  import android.net.rtp.AudioGroup;
  import android.net.sip.SipAudioCall;
  import android.net.sip.SipErrorCode;
  import android.net.sip.SipException;
  import android.net.sip.SipManager;
  import android.net.sip.SipProfile;
  import android.net.sip.SipSession;
  import android.os.AsyncResult;
  import android.os.Message;
  import android.telephony.DisconnectCause;
  import android.telephony.PhoneNumberUtils;
  import android.telephony.ServiceState;
  import android.text.TextUtils;
  import android.telephony.Rlog;
  
  import com.android.internal.telephony.Call;
  import com.android.internal.telephony.CallStateException;
  import com.android.internal.telephony.Connection;
  import com.android.internal.telephony.Phone;
  import com.android.internal.telephony.PhoneConstants;
  import com.android.internal.telephony.PhoneNotifier;
  
  import java.text.ParseException;
  import java.util.List;
  import java.util.regex.Pattern;
  
  /**
   * {@hide}
   */
  public class SipPhone extends SipPhoneBase {
      private static final String LOG_TAG = "SipPhone";
      private static final boolean DBG = true;
      private static final boolean VDBG = false; // STOPSHIP if true
      private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
      private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
      private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
  
      // A call that is ringing or (call) waiting
      private SipCall mRingingCall = new SipCall();
      private SipCall mForegroundCall = new SipCall();
      private SipCall mBackgroundCall = new SipCall();
  
      private SipManager mSipManager;
      private SipProfile mProfile;
  
      SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
          super("SIP:" + profile.getUriString(), context, notifier);
  
          if (DBG) log("new SipPhone: " + profile.getUriString());
          mRingingCall = new SipCall();
          mForegroundCall = new SipCall();
          mBackgroundCall = new SipCall();
          mProfile = profile;
          mSipManager = SipManager.newInstance(context);
      }
  
      @Override
      public boolean equals(Object o) {
          if (o == this) return true;
          if (!(o instanceof SipPhone)) return false;
          SipPhone that = (SipPhone) o;
          return mProfile.getUriString().equals(that.mProfile.getUriString());
      }
  
      public String getSipUri() {
          return mProfile.getUriString();
      }
  
      public boolean equals(SipPhone phone) {
          return getSipUri().equals(phone.getSipUri());
      }
  
      public Connection takeIncomingCall(Object incomingCall) {
          // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
          // Also there are many things not synchronized, of course
          // this may be true of CdmaPhone and GsmPhone too!!!
          synchronized (SipPhone.class) {
              if (!(incomingCall instanceof SipAudioCall)) {
                  if (DBG) log("takeIncomingCall: ret=null, not a SipAudioCall");
                  return null;
              }
              if (mRingingCall.getState().isAlive()) {
                  if (DBG) log("takeIncomingCall: ret=null, ringingCall not alive");
                  return null;
              }
  
              // FIXME: is it true that we cannot take any incoming call if
              // both foreground and background are active
              if (mForegroundCall.getState().isAlive()
                      && mBackgroundCall.getState().isAlive()) {
                  if (DBG) {
                      log("takeIncomingCall: ret=null," + " foreground and background both alive");
                  }
                  return null;
              }
  
              try {
                  SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
                  if (DBG) log("takeIncomingCall: taking call from: "
                          + sipAudioCall.getPeerProfile().getUriString());
                  String localUri = sipAudioCall.getLocalProfile().getUriString();
                  if (localUri.equals(mProfile.getUriString())) {
                      boolean makeCallWait = mForegroundCall.getState().isAlive();
                      SipConnection connection = mRingingCall.initIncomingCall(sipAudioCall,
                              makeCallWait);
                      if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) {
                          // Peer cancelled the call!
                          if (DBG) log("    takeIncomingCall: call cancelled !!");
                          mRingingCall.reset();
                          connection = null;
                      }
                      return connection;
                  }
              } catch (Exception e) {
                  // Peer may cancel the call at any time during the time we hook
                  // up ringingCall with sipAudioCall. Clean up ringingCall when
                  // that happens.
                  if (DBG) log("    takeIncomingCall: exception e=" + e);
                  mRingingCall.reset();
              }
              if (DBG) log("takeIncomingCall: NOT taking !!");
              return null;
          }
      }
  
      @Override
      public void acceptCall(int videoState) throws CallStateException {
          synchronized (SipPhone.class) {
              if ((mRingingCall.getState() == Call.State.INCOMING) ||
                      (mRingingCall.getState() == Call.State.WAITING)) {
                  if (DBG) log("acceptCall: accepting");
                  // Always unmute when answering a new call
                  mRingingCall.setMute(false);
                  mRingingCall.acceptCall();
              } else {
                  if (DBG) {
                      log("acceptCall:" +
                          " throw CallStateException(\"phone not ringing\")");
                  }
                  throw new CallStateException("phone not ringing");
              }
          }
      }
  
      @Override
      public void rejectCall() throws CallStateException {
          synchronized (SipPhone.class) {
              if (mRingingCall.getState().isRinging()) {
                  if (DBG) log("rejectCall: rejecting");
                  mRingingCall.rejectCall();
              } else {
                  if (DBG) {
                      log("rejectCall:" +
                          " throw CallStateException(\"phone not ringing\")");
                  }
                  throw new CallStateException("phone not ringing");
              }
          }
      }
  
      @Override
      public Connection dial(String dialString, int videoState) throws CallStateException {
          synchronized (SipPhone.class) {
              return dialInternal(dialString, videoState);
          }
      }
  
      private Connection dialInternal(String dialString, int videoState)
              throws CallStateException {
          if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx"));
          clearDisconnected();
  
          if (!canDial()) {
              throw new CallStateException("dialInternal: cannot dial in current state");
          }
          if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
              switchHoldingAndActive();
          }
          if (mForegroundCall.getState() != SipCall.State.IDLE) {
              //we should have failed in !canDial() above before we get here
              throw new CallStateException("cannot dial in current state");
          }
  
          mForegroundCall.setMute(false);
          try {
              Connection c = mForegroundCall.dial(dialString);
              return c;
          } catch (SipException e) {
              loge("dialInternal: ", e);
              throw new CallStateException("dial error: " + e);
          }
      }
  
      @Override
      public void switchHoldingAndActive() throws CallStateException {
          if (DBG) log("dialInternal: switch fg and bg");
          synchronized (SipPhone.class) {
              mForegroundCall.switchWith(mBackgroundCall);
              if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
              if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
          }
      }
  
      @Override
      public boolean canConference() {
          if (DBG) log("canConference: ret=true");
          return true;
      }
  
      @Override
      public void conference() throws CallStateException {
          synchronized (SipPhone.class) {
              if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
                      || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
                  throw new CallStateException("wrong state to merge calls: fg="
                          + mForegroundCall.getState() + ", bg="
                          + mBackgroundCall.getState());
              }
              if (DBG) log("conference: merge fg & bg");
              mForegroundCall.merge(mBackgroundCall);
          }
      }
  
      public void conference(Call that) throws CallStateException {
          synchronized (SipPhone.class) {
              if (!(that instanceof SipCall)) {
                  throw new CallStateException("expect " + SipCall.class
                          + ", cannot merge with " + that.getClass());
              }
              mForegroundCall.merge((SipCall) that);
          }
      }
  
      @Override
      public boolean canTransfer() {
          return false;
      }
  
      @Override
      public void explicitCallTransfer() {
          //mCT.explicitCallTransfer();
      }
  
      @Override
      public void clearDisconnected() {
          synchronized (SipPhone.class) {
              mRingingCall.clearDisconnected();
              mForegroundCall.clearDisconnected();
              mBackgroundCall.clearDisconnected();
  
              updatePhoneState();
              notifyPreciseCallStateChanged();
          }
      }
  
      @Override
      public void sendDtmf(char c) {
          if (!PhoneNumberUtils.is12Key(c)) {
              loge("sendDtmf called with invalid character '" + c + "'");
          } else if (mForegroundCall.getState().isAlive()) {
              synchronized (SipPhone.class) {
                  mForegroundCall.sendDtmf(c);
              }
          }
      }
  
      @Override
      public void startDtmf(char c) {
          if (!PhoneNumberUtils.is12Key(c)) {
              loge("startDtmf called with invalid character '" + c + "'");
          } else {
              sendDtmf(c);
          }
      }
  
      @Override
      public void stopDtmf() {
          // no op
      }
  
      public void sendBurstDtmf(String dtmfString) {
          loge("sendBurstDtmf() is a CDMA method");
      }
  
      @Override
      public void getOutgoingCallerIdDisplay(Message onComplete) {
          // FIXME: what to reply?
          AsyncResult.forMessage(onComplete, null, null);
          onComplete.sendToTarget();
      }
  
      @Override
      public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
                                             Message onComplete) {
          // FIXME: what's this for SIP?
          AsyncResult.forMessage(onComplete, null, null);
          onComplete.sendToTarget();
      }
  
      @Override
      public void getCallWaiting(Message onComplete) {
          // FIXME: what to reply?
          AsyncResult.forMessage(onComplete, null, null);
          onComplete.sendToTarget();
      }
  
      @Override
      public void setCallWaiting(boolean enable, Message onComplete) {
          // FIXME: what to reply?
          loge("call waiting not supported");
      }
  
      @Override
      public void setEchoSuppressionEnabled() {
          // Echo suppression may not be available on every device. So, check
          // whether it is supported
          synchronized (SipPhone.class) {
              AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
              String echoSuppression = audioManager.getParameters("ec_supported");
              if (echoSuppression.contains("off")) {
                  mForegroundCall.setAudioGroupMode();
              }
          }
      }
  
      @Override
      public void setMute(boolean muted) {
          synchronized (SipPhone.class) {
              mForegroundCall.setMute(muted);
          }
      }
  
      @Override
      public boolean getMute() {
          return (mForegroundCall.getState().isAlive()
                  ? mForegroundCall.getMute()
                  : mBackgroundCall.getMute());
      }
  
      @Override
      public Call getForegroundCall() {
          return mForegroundCall;
      }
  
      @Override
      public Call getBackgroundCall() {
          return mBackgroundCall;
      }
  
      @Override
      public Call getRingingCall() {
          return mRingingCall;
      }
  
      @Override
      public ServiceState getServiceState() {
          // FIXME: we may need to provide this when data connectivity is lost
          // or when server is down
          return super.getServiceState();
      }
  
      private String getUriString(SipProfile p) {
          // SipProfile.getUriString() may contain "SIP:" and port
  