Home | History | Annotate | Download | only in sip
      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 android.net.sip;
     18 
     19 import android.content.Context;
     20 import android.media.AudioManager;
     21 import android.net.rtp.AudioCodec;
     22 import android.net.rtp.AudioGroup;
     23 import android.net.rtp.AudioStream;
     24 import android.net.rtp.RtpStream;
     25 import android.net.sip.SimpleSessionDescription.Media;
     26 import android.net.wifi.WifiManager;
     27 import android.os.Message;
     28 import android.telephony.Rlog;
     29 import android.text.TextUtils;
     30 import java.io.IOException;
     31 import java.net.InetAddress;
     32 import java.net.UnknownHostException;
     33 
     34 /**
     35  * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager},
     36  * using {@link SipManager#makeAudioCall makeAudioCall()} and  {@link SipManager#takeAudioCall
     37  * takeAudioCall()}.
     38  *
     39  * <p class="note"><strong>Note:</strong> Using this class require the
     40  *   {@link android.Manifest.permission#INTERNET} and
     41  *   {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link
     42  *   #startAudio} requires the
     43  *   {@link android.Manifest.permission#RECORD_AUDIO},
     44  *   {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and
     45  *   {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode
     46  *   setSpeakerMode()} requires the
     47  *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
     48  *
     49  * <div class="special reference">
     50  * <h3>Developer Guides</h3>
     51  * <p>For more information about using SIP, read the
     52  * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
     53  * developer guide.</p>
     54  * </div>
     55  */
     56 public class SipAudioCall {
     57     private static final String LOG_TAG = SipAudioCall.class.getSimpleName();
     58     private static final boolean DBG = true;
     59     private static final boolean RELEASE_SOCKET = true;
     60     private static final boolean DONT_RELEASE_SOCKET = false;
     61     private static final int SESSION_TIMEOUT = 5; // in seconds
     62     private static final int TRANSFER_TIMEOUT = 15; // in seconds
     63 
     64     /** Listener for events relating to a SIP call, such as when a call is being
     65      * recieved ("on ringing") or a call is outgoing ("on calling").
     66      * <p>Many of these events are also received by {@link SipSession.Listener}.</p>
     67      */
     68     public static class Listener {
     69         /**
     70          * Called when the call object is ready to make another call.
     71          * The default implementation calls {@link #onChanged}.
     72          *
     73          * @param call the call object that is ready to make another call
     74          */
     75         public void onReadyToCall(SipAudioCall call) {
     76             onChanged(call);
     77         }
     78 
     79         /**
     80          * Called when a request is sent out to initiate a new call.
     81          * The default implementation calls {@link #onChanged}.
     82          *
     83          * @param call the call object that carries out the audio call
     84          */
     85         public void onCalling(SipAudioCall call) {
     86             onChanged(call);
     87         }
     88 
     89         /**
     90          * Called when a new call comes in.
     91          * The default implementation calls {@link #onChanged}.
     92          *
     93          * @param call the call object that carries out the audio call
     94          * @param caller the SIP profile of the caller
     95          */
     96         public void onRinging(SipAudioCall call, SipProfile caller) {
     97             onChanged(call);
     98         }
     99 
    100         /**
    101          * Called when a RINGING response is received for the INVITE request
    102          * sent. The default implementation calls {@link #onChanged}.
    103          *
    104          * @param call the call object that carries out the audio call
    105          */
    106         public void onRingingBack(SipAudioCall call) {
    107             onChanged(call);
    108         }
    109 
    110         /**
    111          * Called when the session is established.
    112          * The default implementation calls {@link #onChanged}.
    113          *
    114          * @param call the call object that carries out the audio call
    115          */
    116         public void onCallEstablished(SipAudioCall call) {
    117             onChanged(call);
    118         }
    119 
    120         /**
    121          * Called when the session is terminated.
    122          * The default implementation calls {@link #onChanged}.
    123          *
    124          * @param call the call object that carries out the audio call
    125          */
    126         public void onCallEnded(SipAudioCall call) {
    127             onChanged(call);
    128         }
    129 
    130         /**
    131          * Called when the peer is busy during session initialization.
    132          * The default implementation calls {@link #onChanged}.
    133          *
    134          * @param call the call object that carries out the audio call
    135          */
    136         public void onCallBusy(SipAudioCall call) {
    137             onChanged(call);
    138         }
    139 
    140         /**
    141          * Called when the call is on hold.
    142          * The default implementation calls {@link #onChanged}.
    143          *
    144          * @param call the call object that carries out the audio call
    145          */
    146         public void onCallHeld(SipAudioCall call) {
    147             onChanged(call);
    148         }
    149 
    150         /**
    151          * Called when an error occurs. The default implementation is no op.
    152          *
    153          * @param call the call object that carries out the audio call
    154          * @param errorCode error code of this error
    155          * @param errorMessage error message
    156          * @see SipErrorCode
    157          */
    158         public void onError(SipAudioCall call, int errorCode,
    159                 String errorMessage) {
    160             // no-op
    161         }
    162 
    163         /**
    164          * Called when an event occurs and the corresponding callback is not
    165          * overridden. The default implementation is no op. Error events are
    166          * not re-directed to this callback and are handled in {@link #onError}.
    167          */
    168         public void onChanged(SipAudioCall call) {
    169             // no-op
    170         }
    171     }
    172 
    173     private Context mContext;
    174     private SipProfile mLocalProfile;
    175     private SipAudioCall.Listener mListener;
    176     private SipSession mSipSession;
    177     private SipSession mTransferringSession;
    178 
    179     private long mSessionId = System.currentTimeMillis();
    180     private String mPeerSd;
    181 
    182     private AudioStream mAudioStream;
    183     private AudioGroup mAudioGroup;
    184 
    185     private boolean mInCall = false;
    186     private boolean mMuted = false;
    187     private boolean mHold = false;
    188 
    189     private WifiManager mWm;
    190     private WifiManager.WifiLock mWifiHighPerfLock;
    191 
    192     private int mErrorCode = SipErrorCode.NO_ERROR;
    193     private String mErrorMessage;
    194 
    195     /**
    196      * Creates a call object with the local SIP profile.
    197      * @param context the context for accessing system services such as
    198      *        ringtone, audio, WIFI etc
    199      */
    200     public SipAudioCall(Context context, SipProfile localProfile) {
    201         mContext = context;
    202         mLocalProfile = localProfile;
    203         mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    204     }
    205 
    206     /**
    207      * Sets the listener to listen to the audio call events. The method calls
    208      * {@link #setListener setListener(listener, false)}.
    209      *
    210      * @param listener to listen to the audio call events of this object
    211      * @see #setListener(Listener, boolean)
    212      */
    213     public void setListener(SipAudioCall.Listener listener) {
    214         setListener(listener, false);
    215     }
    216 
    217     /**
    218      * Sets the listener to listen to the audio call events. A
    219      * {@link SipAudioCall} can only hold one listener at a time. Subsequent
    220      * calls to this method override the previous listener.
    221      *
    222      * @param listener to listen to the audio call events of this object
    223      * @param callbackImmediately set to true if the caller wants to be called
    224      *      back immediately on the current state
    225      */
    226     public void setListener(SipAudioCall.Listener listener,
    227             boolean callbackImmediately) {
    228         mListener = listener;
    229         try {
    230             if ((listener == null) || !callbackImmediately) {
    231                 // do nothing
    232             } else if (mErrorCode != SipErrorCode.NO_ERROR) {
    233                 listener.onError(this, mErrorCode, mErrorMessage);
    234             } else if (mInCall) {
    235                 if (mHold) {
    236                     listener.onCallHeld(this);
    237                 } else {
    238                     listener.onCallEstablished(this);
    239                 }
    240             } else {
    241                 int state = getState();
    242                 switch (state) {
    243                     case SipSession.State.READY_TO_CALL:
    244                         listener.onReadyToCall(this);
    245                         break;
    246                     case SipSession.State.INCOMING_CALL:
    247                         listener.onRinging(this, getPeerProfile());
    248                         break;
    249                     case SipSession.State.OUTGOING_CALL:
    250                         listener.onCalling(this);
    251                         break;
    252                     case SipSession.State.OUTGOING_CALL_RING_BACK:
    253                         listener.onRingingBack(this);
    254                         break;
    255                 }
    256             }
    257         } catch (Throwable t) {
    258             loge("setListener()", t);
    259         }
    260     }
    261 
    262     /**
    263      * Checks if the call is established.
    264      *
    265      * @return true if the call is established
    266      */
    267     public boolean isInCall() {
    268         synchronized (this) {
    269             return mInCall;
    270         }
    271     }
    272 
    273     /**
    274      * Checks if the call is on hold.
    275      *
    276      * @return true if the call is on hold
    277      */
    278     public boolean isOnHold() {
    279         synchronized (this) {
    280             return mHold;
    281         }
    282     }
    283 
    284     /**
    285      * Closes this object. This object is not usable after being closed.
    286      */
    287     public void close() {
    288         close(true);
    289     }
    290 
    291     private synchronized void close(boolean closeRtp) {
    292         if (closeRtp) stopCall(RELEASE_SOCKET);
    293 
    294         mInCall = false;
    295         mHold = false;
    296         mSessionId = System.currentTimeMillis();
    297         mErrorCode = SipErrorCode.NO_ERROR;
    298         mErrorMessage = null;
    299 
    300         if (mSipSession != null) {
    301             mSipSession.setListener(null);
    302             mSipSession = null;
    303         }
    304     }
    305 
    306     /**
    307      * Gets the local SIP profile.
    308      *
    309      * @return the local SIP profile
    310      */
    311     public SipProfile getLocalProfile() {
    312         synchronized (this) {
    313             return mLocalProfile;
    314         }
    315     }
    316 
    317     /**
    318      * Gets the peer's SIP profile.
    319      *
    320      * @return the peer's SIP profile
    321      */
    322     public SipProfile getPeerProfile() {
    323         synchronized (this) {
    324             return (mSipSession == null) ? null : mSipSession.getPeerProfile();
    325         }
    326     }
    327 
    328     /**
    329      * Gets the state of the {@link SipSession} that carries this call.
    330      * The value returned must be one of the states in {@link SipSession.State}.
    331      *
    332      * @return the session state
    333      */
    334     public int getState() {
    335         synchronized (this) {
    336             if (mSipSession == null) return SipSession.State.READY_TO_CALL;
    337             return mSipSession.getState();
    338         }
    339     }
    340 
    341 
    342     /**
    343      * Gets the {@link SipSession} that carries this call.
    344      *
    345      * @return the session object that carries this call
    346      * @hide
    347      */
    348     public SipSession getSipSession() {
    349         synchronized (this) {
    350             return mSipSession;
    351         }
    352     }
    353 
    354     private synchronized void transferToNewSession() {
    355         if (mTransferringSession == null) return;
    356         SipSession origin = mSipSession;
    357         mSipSession = mTransferringSession;
    358         mTransferringSession = null;
    359 
    360         // stop the replaced call.
    361         if (mAudioStream != null) {
    362             mAudioStream.join(null);
    363         } else {
    364             try {
    365                 mAudioStream = new AudioStream(InetAddress.getByName(
    366                         getLocalIp()));
    367             } catch (Throwable t) {
    368                 loge("transferToNewSession():", t);
    369             }
    370         }
    371         if (origin != null) origin.endCall();
    372         startAudio();
    373     }
    374 
    375     private SipSession.Listener createListener() {
    376         return new SipSession.Listener() {
    377             @Override
    378             public void onCalling(SipSession session) {
    379                 if (DBG) log("onCalling: session=" + session);
    380                 Listener listener = mListener;
    381                 if (listener != null) {
    382                     try {
    383                         listener.onCalling(SipAudioCall.this);
    384                     } catch (Throwable t) {
    385                         loge("onCalling():", t);
    386                     }
    387                 }
    388             }
    389 
    390             @Override
    391             public void onRingingBack(SipSession session) {
    392                 if (DBG) log("onRingingBackk: " + session);
    393                 Listener listener = mListener;
    394                 if (listener != null) {
    395                     try {
    396                         listener.onRingingBack(SipAudioCall.this);
    397                     } catch (Throwable t) {
    398                         loge("onRingingBack():", t);
    399                     }
    400                 }
    401             }
    402 
    403             @Override
    404             public void onRinging(SipSession session,
    405                     SipProfile peerProfile, String sessionDescription) {
    406                 // this callback is triggered only for reinvite.
    407                 synchronized (SipAudioCall.this) {
    408                     if ((mSipSession == null) || !mInCall
    409                             || !session.getCallId().equals(
    410                                     mSipSession.getCallId())) {
    411                         // should not happen
    412                         session.endCall();
    413                         return;
    414                     }
    415 
    416                     // session changing request
    417                     try {
    418                         String answer = createAnswer(sessionDescription).encode();
    419                         mSipSession.answerCall(answer, SESSION_TIMEOUT);
    420                     } catch (Throwable e) {
    421                         loge("onRinging():", e);
    422                         session.endCall();
    423                     }
    424                 }
    425             }
    426 
    427             @Override
    428             public void onCallEstablished(SipSession session,
    429                     String sessionDescription) {
    430                 mPeerSd = sessionDescription;
    431                 if (DBG) log("onCallEstablished(): " + mPeerSd);
    432 
    433                 // TODO: how to notify the UI that the remote party is changed
    434                 if ((mTransferringSession != null)
    435                         && (session == mTransferringSession)) {
    436                     transferToNewSession();
    437                     return;
    438                 }
    439 
    440                 Listener listener = mListener;
    441                 if (listener != null) {
    442                     try {
    443                         if (mHold) {
    444                             listener.onCallHeld(SipAudioCall.this);
    445                         } else {
    446                             listener.onCallEstablished(SipAudioCall.this);
    447                         }
    448                     } catch (Throwable t) {
    449                         loge("onCallEstablished(): ", t);
    450                     }
    451                 }
    452             }
    453 
    454             @Override
    455             public void onCallEnded(SipSession session) {
    456                 if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession);
    457                 // reset the trasnferring session if it is the one.
    458                 if (session == mTransferringSession) {
    459                     mTransferringSession = null;
    460                     return;
    461                 }
    462                 // or ignore the event if the original session is being
    463                 // transferred to the new one.
    464                 if ((mTransferringSession != null) ||
    465                     (session != mSipSession)) return;
    466 
    467                 Listener listener = mListener;
    468                 if (listener != null) {
    469                     try {
    470                         listener.onCallEnded(SipAudioCall.this);
    471                     } catch (Throwable t) {
    472                         loge("onCallEnded(): ", t);
    473                     }
    474                 }
    475                 close();
    476             }
    477 
    478             @Override
    479             public void onCallBusy(SipSession session) {
    480                 if (DBG) log("onCallBusy: " + session);
    481                 Listener listener = mListener;
    482                 if (listener != null) {
    483                     try {
    484                         listener.onCallBusy(SipAudioCall.this);
    485                     } catch (Throwable t) {
    486                         loge("onCallBusy(): ", t);
    487                     }
    488                 }
    489                 close(false);
    490             }
    491 
    492             @Override
    493             public void onCallChangeFailed(SipSession session, int errorCode,
    494                     String message) {
    495                 if (DBG) log("onCallChangedFailed: " + message);
    496                 mErrorCode = errorCode;
    497                 mErrorMessage = message;
    498                 Listener listener = mListener;
    499                 if (listener != null) {
    500                     try {
    501                         listener.onError(SipAudioCall.this, mErrorCode,
    502                                 message);
    503                     } catch (Throwable t) {
    504                         loge("onCallBusy():", t);
    505                     }
    506                 }
    507             }
    508 
    509             @Override
    510             public void onError(SipSession session, int errorCode,
    511                     String message) {
    512                 SipAudioCall.this.onError(errorCode, message);
    513             }
    514 
    515             @Override
    516             public void onRegistering(SipSession session) {
    517                 // irrelevant
    518             }
    519 
    520             @Override
    521             public void onRegistrationTimeout(SipSession session) {
    522                 // irrelevant
    523             }
    524 
    525             @Override
    526             public void onRegistrationFailed(SipSession session, int errorCode,
    527                     String message) {
    528                 // irrelevant
    529             }
    530 
    531             @Override
    532             public void onRegistrationDone(SipSession session, int duration) {
    533                 // irrelevant
    534             }
    535 
    536             @Override
    537             public void onCallTransferring(SipSession newSession,
    538                     String sessionDescription) {
    539                 if (DBG) log("onCallTransferring: mSipSession="
    540                         + mSipSession + " newSession=" + newSession);
    541                 mTransferringSession = newSession;
    542                 try {
    543                     if (sessionDescription == null) {
    544                         newSession.makeCall(newSession.getPeerProfile(),
    545                                 createOffer().encode(), TRANSFER_TIMEOUT);
    546                     } else {
    547                         String answer = createAnswer(sessionDescription).encode();
    548                         newSession.answerCall(answer, SESSION_TIMEOUT);
    549                     }
    550                 } catch (Throwable e) {
    551                     loge("onCallTransferring()", e);
    552                     newSession.endCall();
    553                 }
    554             }
    555         };
    556     }
    557 
    558     private void onError(int errorCode, String message) {
    559         if (DBG) log("onError: "
    560                 + SipErrorCode.toString(errorCode) + ": " + message);
    561         mErrorCode = errorCode;
    562         mErrorMessage = message;
    563         Listener listener = mListener;
    564         if (listener != null) {
    565             try {
    566                 listener.onError(this, errorCode, message);
    567             } catch (Throwable t) {
    568                 loge("onError():", t);
    569             }
    570         }
    571         synchronized (this) {
    572             if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
    573                     || !isInCall()) {
    574                 close(true);
    575             }
    576         }
    577     }
    578 
    579     /**
    580      * Attaches an incoming call to this call object.
    581      *
    582      * @param session the session that receives the incoming call
    583      * @param sessionDescription the session description of the incoming call
    584      * @throws SipException if the SIP service fails to attach this object to
    585      *        the session or VOIP API is not supported by the device
    586      * @see SipManager#isVoipSupported
    587      */
    588     public void attachCall(SipSession session, String sessionDescription)
    589             throws SipException {
    590         if (!SipManager.isVoipSupported(mContext)) {
    591             throw new SipException("VOIP API is not supported");
    592         }
    593 
    594         synchronized (this) {
    595             mSipSession = session;
    596             mPeerSd = sessionDescription;
    597             if (DBG) log("attachCall(): " + mPeerSd);
    598             try {
    599                 session.setListener(createListener());
    600             } catch (Throwable e) {
    601                 loge("attachCall()", e);
    602                 throwSipException(e);
    603             }
    604         }
    605     }
    606 
    607     /**
    608      * Initiates an audio call to the specified profile. The attempt will be
    609      * timed out if the call is not established within {@code timeout} seconds
    610      * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    611      * will be called.
    612      *
    613      * @param peerProfile the SIP profile to make the call to
    614      * @param sipSession the {@link SipSession} for carrying out the call
    615      * @param timeout the timeout value in seconds. Default value (defined by
    616      *        SIP protocol) is used if {@code timeout} is zero or negative.
    617      * @see Listener#onError
    618      * @throws SipException if the SIP service fails to create a session for the
    619      *        call or VOIP API is not supported by the device
    620      * @see SipManager#isVoipSupported
    621      */
    622     public void makeCall(SipProfile peerProfile, SipSession sipSession,
    623             int timeout) throws SipException {
    624         if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout);
    625         if (!SipManager.isVoipSupported(mContext)) {
    626             throw new SipException("VOIP API is not supported");
    627         }
    628 
    629         synchronized (this) {
    630             mSipSession = sipSession;
    631             try {
    632                 mAudioStream = new AudioStream(InetAddress.getByName(
    633                         getLocalIp()));
    634                 sipSession.setListener(createListener());
    635                 sipSession.makeCall(peerProfile, createOffer().encode(),
    636                         timeout);
    637             } catch (IOException e) {
    638                 loge("makeCall:", e);
    639                 throw new SipException("makeCall()", e);
    640             }
    641         }
    642     }
    643 
    644     /**
    645      * Ends a call.
    646      * @throws SipException if the SIP service fails to end the call
    647      */
    648     public void endCall() throws SipException {
    649         if (DBG) log("endCall: mSipSession" + mSipSession);
    650         synchronized (this) {
    651             stopCall(RELEASE_SOCKET);
    652             mInCall = false;
    653 
    654             // perform the above local ops first and then network op
    655             if (mSipSession != null) mSipSession.endCall();
    656         }
    657     }
    658 
    659     /**
    660      * Puts a call on hold.  When succeeds, {@link Listener#onCallHeld} is
    661      * called. The attempt will be timed out if the call is not established
    662      * within {@code timeout} seconds and
    663      * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    664      * will be called.
    665      *
    666      * @param timeout the timeout value in seconds. Default value (defined by
    667      *        SIP protocol) is used if {@code timeout} is zero or negative.
    668      * @see Listener#onError
    669      * @throws SipException if the SIP service fails to hold the call
    670      */
    671     public void holdCall(int timeout) throws SipException {
    672         if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout);
    673         synchronized (this) {
    674             if (mHold) return;
    675             if (mSipSession == null) {
    676                 loge("holdCall:");
    677                 throw new SipException("Not in a call to hold call");
    678             }
    679             mSipSession.changeCall(createHoldOffer().encode(), timeout);
    680             mHold = true;
    681             setAudioGroupMode();
    682         }
    683     }
    684 
    685     /**
    686      * Answers a call. The attempt will be timed out if the call is not
    687      * established within {@code timeout} seconds and
    688      * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    689      * will be called.
    690      *
    691      * @param timeout the timeout value in seconds. Default value (defined by
    692      *        SIP protocol) is used if {@code timeout} is zero or negative.
    693      * @see Listener#onError
    694      * @throws SipException if the SIP service fails to answer the call
    695      */
    696     public void answerCall(int timeout) throws SipException {
    697         if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout);
    698         synchronized (this) {
    699             if (mSipSession == null) {
    700                 throw new SipException("No call to answer");
    701             }
    702             try {
    703                 mAudioStream = new AudioStream(InetAddress.getByName(
    704                         getLocalIp()));
    705                 mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
    706             } catch (IOException e) {
    707                 loge("answerCall:", e);
    708                 throw new SipException("answerCall()", e);
    709             }
    710         }
    711     }
    712 
    713     /**
    714      * Continues a call that's on hold. When succeeds,
    715      * {@link Listener#onCallEstablished} is called. The attempt will be timed
    716      * out if the call is not established within {@code timeout} seconds and
    717      * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    718      * will be called.
    719      *
    720      * @param timeout the timeout value in seconds. Default value (defined by
    721      *        SIP protocol) is used if {@code timeout} is zero or negative.
    722      * @see Listener#onError
    723      * @throws SipException if the SIP service fails to unhold the call
    724      */
    725     public void continueCall(int timeout) throws SipException {
    726         if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout);
    727         synchronized (this) {
    728             if (!mHold) return;
    729             mSipSession.changeCall(createContinueOffer().encode(), timeout);
    730             mHold = false;
    731             setAudioGroupMode();
    732         }
    733     }
    734 
    735     private SimpleSessionDescription createOffer() {
    736         SimpleSessionDescription offer =
    737                 new SimpleSessionDescription(mSessionId, getLocalIp());
    738         AudioCodec[] codecs = AudioCodec.getCodecs();
    739         Media media = offer.newMedia(
    740                 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
    741         for (AudioCodec codec : AudioCodec.getCodecs()) {
    742             media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
    743         }
    744         media.setRtpPayload(127, "telephone-event/8000", "0-15");
    745         if (DBG) log("createOffer: offer=" + offer);
    746         return offer;
    747     }
    748 
    749     private SimpleSessionDescription createAnswer(String offerSd) {
    750         if (TextUtils.isEmpty(offerSd)) return createOffer();
    751         SimpleSessionDescription offer =
    752                 new SimpleSessionDescription(offerSd);
    753         SimpleSessionDescription answer =
    754                 new SimpleSessionDescription(mSessionId, getLocalIp());
    755         AudioCodec codec = null;
    756         for (Media media : offer.getMedia()) {
    757             if ((codec == null) && (media.getPort() > 0)
    758                     && "audio".equals(media.getType())
    759                     && "RTP/AVP".equals(media.getProtocol())) {
    760                 // Find the first audio codec we supported.
    761                 for (int type : media.getRtpPayloadTypes()) {
    762                     codec = AudioCodec.getCodec(type, media.getRtpmap(type),
    763                             media.getFmtp(type));
    764                     if (codec != null) {
    765                         break;
    766                     }
    767                 }
    768                 if (codec != null) {
    769                     Media reply = answer.newMedia(
    770                             "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
    771                     reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
    772 
    773                     // Check if DTMF is supported in the same media.
    774                     for (int type : media.getRtpPayloadTypes()) {
    775                         String rtpmap = media.getRtpmap(type);
    776                         if ((type != codec.type) && (rtpmap != null)
    777                                 && rtpmap.startsWith("telephone-event")) {
    778                             reply.setRtpPayload(
    779                                     type, rtpmap, media.getFmtp(type));
    780                         }
    781                     }
    782 
    783                     // Handle recvonly and sendonly.
    784                     if (media.getAttribute("recvonly") != null) {
    785                         answer.setAttribute("sendonly", "");
    786                     } else if(media.getAttribute("sendonly") != null) {
    787                         answer.setAttribute("recvonly", "");
    788                     } else if(offer.getAttribute("recvonly") != null) {
    789                         answer.setAttribute("sendonly", "");
    790                     } else if(offer.getAttribute("sendonly") != null) {
    791                         answer.setAttribute("recvonly", "");
    792                     }
    793                     continue;
    794                 }
    795             }
    796             // Reject the media.
    797             Media reply = answer.newMedia(
    798                     media.getType(), 0, 1, media.getProtocol());
    799             for (String format : media.getFormats()) {
    800                 reply.setFormat(format, null);
    801             }
    802         }
    803         if (codec == null) {
    804             loge("createAnswer: no suitable codes");
    805             throw new IllegalStateException("Reject SDP: no suitable codecs");
    806         }
    807         if (DBG) log("createAnswer: answer=" + answer);
    808         return answer;
    809     }
    810 
    811     private SimpleSessionDescription createHoldOffer() {
    812         SimpleSessionDescription offer = createContinueOffer();
    813         offer.setAttribute("sendonly", "");
    814         if (DBG) log("createHoldOffer: offer=" + offer);
    815         return offer;
    816     }
    817 
    818     private SimpleSessionDescription createContinueOffer() {
    819         if (DBG) log("createContinueOffer");
    820         SimpleSessionDescription offer =
    821                 new SimpleSessionDescription(mSessionId, getLocalIp());
    822         Media media = offer.newMedia(
    823                 "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
    824         AudioCodec codec = mAudioStream.getCodec();
    825         media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
    826         int dtmfType = mAudioStream.getDtmfType();
    827         if (dtmfType != -1) {
    828             media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
    829         }
    830         return offer;
    831     }
    832 
    833     private void grabWifiHighPerfLock() {
    834         if (mWifiHighPerfLock == null) {
    835             if (DBG) log("grabWifiHighPerfLock:");
    836             mWifiHighPerfLock = ((WifiManager)
    837                     mContext.getSystemService(Context.WIFI_SERVICE))
    838                     .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG);
    839             mWifiHighPerfLock.acquire();
    840         }
    841     }
    842 
    843     private void releaseWifiHighPerfLock() {
    844         if (mWifiHighPerfLock != null) {
    845             if (DBG) log("releaseWifiHighPerfLock:");
    846             mWifiHighPerfLock.release();
    847             mWifiHighPerfLock = null;
    848         }
    849     }
    850 
    851     private boolean isWifiOn() {
    852         return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
    853     }
    854 
    855     /** Toggles mute. */
    856     public void toggleMute() {
    857         synchronized (this) {
    858             mMuted = !mMuted;
    859             setAudioGroupMode();
    860         }
    861     }
    862 
    863     /**
    864      * Checks if the call is muted.
    865      *
    866      * @return true if the call is muted
    867      */
    868     public boolean isMuted() {
    869         synchronized (this) {
    870             return mMuted;
    871         }
    872     }
    873 
    874     /**
    875      * Puts the device to speaker mode.
    876      * <p class="note"><strong>Note:</strong> Requires the
    877      *   {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p>
    878      *
    879      * @param speakerMode set true to enable speaker mode; false to disable
    880      */
    881     public void setSpeakerMode(boolean speakerMode) {
    882         synchronized (this) {
    883             ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
    884                     .setSpeakerphoneOn(speakerMode);
    885             setAudioGroupMode();
    886         }
    887     }
    888 
    889     private boolean isSpeakerOn() {
    890         return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
    891                 .isSpeakerphoneOn();
    892     }
    893 
    894     /**
    895      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
    896      * event 0--9 maps to decimal
    897      * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
    898      * flash to 16. Currently, event flash is not supported.
    899      *
    900      * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
    901      *        inputs.
    902      */
    903     public void sendDtmf(int code) {
    904         sendDtmf(code, null);
    905     }
    906 
    907     /**
    908      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>,
    909      * event 0--9 maps to decimal
    910      * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
    911      * flash to 16. Currently, event flash is not supported.
    912      *
    913      * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
    914      *        inputs.
    915      * @param result the result message to send when done
    916      */
    917     public void sendDtmf(int code, Message result) {
    918         synchronized (this) {
    919             AudioGroup audioGroup = getAudioGroup();
    920             if ((audioGroup != null) && (mSipSession != null)
    921                     && (SipSession.State.IN_CALL == getState())) {
    922                 if (DBG) log("sendDtmf: code=" + code + " result=" + result);
    923                 audioGroup.sendDtmf(code);
    924             }
    925             if (result != null) result.sendToTarget();
    926         }
    927     }
    928 
    929     /**
    930      * Gets the {@link AudioStream} object used in this call. The object
    931      * represents the RTP stream that carries the audio data to and from the
    932      * peer. The object may not be created before the call is established. And
    933      * it is undefined after the call ends or the {@link #close} method is
    934      * called.
    935      *
    936      * @return the {@link AudioStream} object or null if the RTP stream has not
    937      *      yet been set up
    938      * @hide
    939      */
    940     public AudioStream getAudioStream() {
    941         synchronized (this) {
    942             return mAudioStream;
    943         }
    944     }
    945 
    946     /**
    947      * Gets the {@link AudioGroup} object which the {@link AudioStream} object
    948      * joins. The group object may not exist before the call is established.
    949      * Also, the {@code AudioStream} may change its group during a call (e.g.,
    950      * after the call is held/un-held). Finally, the {@code AudioGroup} object
    951      * returned by this method is undefined after the call ends or the
    952      * {@link #close} method is called. If a group object is set by
    953      * {@link #setAudioGroup(AudioGroup)}, then this method returns that object.
    954      *
    955      * @return the {@link AudioGroup} object or null if the RTP stream has not
    956      *      yet been set up
    957      * @see #getAudioStream
    958      * @hide
    959      */
    960     public AudioGroup getAudioGroup() {
    961         synchronized (this) {
    962             if (mAudioGroup != null) return mAudioGroup;
    963             return ((mAudioStream == null) ? null : mAudioStream.getGroup());
    964         }
    965     }
    966 
    967     /**
    968      * Sets the {@link AudioGroup} object which the {@link AudioStream} object
    969      * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object
    970      * will be dynamically created when needed. Note that the mode of the
    971      * {@code AudioGroup} is not changed according to the audio settings (i.e.,
    972      * hold, mute, speaker phone) of this object. This is mainly used to merge
    973      * multiple {@code SipAudioCall} objects to form a conference call. The
    974      * settings of the first object (that merges others) override others'.
    975      *
    976      * @see #getAudioStream
    977      * @hide
    978      */
    979     public void setAudioGroup(AudioGroup group) {
    980         synchronized (this) {
    981             if (DBG) log("setAudioGroup: group=" + group);
    982             if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
    983                 mAudioStream.join(group);
    984             }
    985             mAudioGroup = group;
    986         }
    987     }
    988 
    989     /**
    990      * Starts the audio for the established call. This method should be called
    991      * after {@link Listener#onCallEstablished} is called.
    992      * <p class="note"><strong>Note:</strong> Requires the
    993      *   {@link android.Manifest.permission#RECORD_AUDIO},
    994      *   {@link android.Manifest.permission#ACCESS_WIFI_STATE} and
    995      *   {@link android.Manifest.permission#WAKE_LOCK} permissions.</p>
    996      */
    997     public void startAudio() {
    998         try {
    999             startAudioInternal();
   1000         } catch (UnknownHostException e) {
   1001             onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
   1002         } catch (Throwable e) {
   1003             onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
   1004         }
   1005     }
   1006 
   1007     private synchronized void startAudioInternal() throws UnknownHostException {
   1008         if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd);
   1009         if (mPeerSd == null) {
   1010             throw new IllegalStateException("mPeerSd = null");
   1011         }
   1012 
   1013         stopCall(DONT_RELEASE_SOCKET);
   1014         mInCall = true;
   1015 
   1016         // Run exact the same logic in createAnswer() to setup mAudioStream.
   1017         SimpleSessionDescription offer =
   1018                 new SimpleSessionDescription(mPeerSd);
   1019         AudioStream stream = mAudioStream;
   1020         AudioCodec codec = null;
   1021         for (Media media : offer.getMedia()) {
   1022             if ((codec == null) && (media.getPort() > 0)
   1023                     && "audio".equals(media.getType())
   1024                     && "RTP/AVP".equals(media.getProtocol())) {
   1025                 // Find the first audio codec we supported.
   1026                 for (int type : media.getRtpPayloadTypes()) {
   1027                     codec = AudioCodec.getCodec(
   1028                             type, media.getRtpmap(type), media.getFmtp(type));
   1029                     if (codec != null) {
   1030                         break;
   1031                     }
   1032                 }
   1033 
   1034                 if (codec != null) {
   1035                     // Associate with the remote host.
   1036                     String address = media.getAddress();
   1037                     if (address == null) {
   1038                         address = offer.getAddress();
   1039                     }
   1040                     stream.associate(InetAddress.getByName(address),
   1041                             media.getPort());
   1042 
   1043                     stream.setDtmfType(-1);
   1044                     stream.setCodec(codec);
   1045                     // Check if DTMF is supported in the same media.
   1046                     for (int type : media.getRtpPayloadTypes()) {
   1047                         String rtpmap = media.getRtpmap(type);
   1048                         if ((type != codec.type) && (rtpmap != null)
   1049                                 && rtpmap.startsWith("telephone-event")) {
   1050                             stream.setDtmfType(type);
   1051                         }
   1052                     }
   1053 
   1054                     // Handle recvonly and sendonly.
   1055                     if (mHold) {
   1056                         stream.setMode(RtpStream.MODE_NORMAL);
   1057                     } else if (media.getAttribute("recvonly") != null) {
   1058                         stream.setMode(RtpStream.MODE_SEND_ONLY);
   1059                     } else if(media.getAttribute("sendonly") != null) {
   1060                         stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
   1061                     } else if(offer.getAttribute("recvonly") != null) {
   1062                         stream.setMode(RtpStream.MODE_SEND_ONLY);
   1063                     } else if(offer.getAttribute("sendonly") != null) {
   1064                         stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
   1065                     } else {
   1066                         stream.setMode(RtpStream.MODE_NORMAL);
   1067                     }
   1068                     break;
   1069                 }
   1070             }
   1071         }
   1072         if (codec == null) {
   1073             throw new IllegalStateException("Reject SDP: no suitable codecs");
   1074         }
   1075 
   1076         if (isWifiOn()) grabWifiHighPerfLock();
   1077 
   1078         // AudioGroup logic:
   1079         AudioGroup audioGroup = getAudioGroup();
   1080         if (mHold) {
   1081             // don't create an AudioGroup here; doing so will fail if
   1082             // there's another AudioGroup out there that's active
   1083         } else {
   1084             if (audioGroup == null) audioGroup = new AudioGroup();
   1085             stream.join(audioGroup);
   1086         }
   1087         setAudioGroupMode();
   1088     }
   1089 
   1090     // set audio group mode based on current audio configuration
   1091     private void setAudioGroupMode() {
   1092         AudioGroup audioGroup = getAudioGroup();
   1093         if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup);
   1094         if (audioGroup != null) {
   1095             if (mHold) {
   1096                 audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
   1097             } else if (mMuted) {
   1098                 audioGroup.setMode(AudioGroup.MODE_MUTED);
   1099             } else if (isSpeakerOn()) {
   1100                 audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
   1101             } else {
   1102                 audioGroup.setMode(AudioGroup.MODE_NORMAL);
   1103             }
   1104         }
   1105     }
   1106 
   1107     private void stopCall(boolean releaseSocket) {
   1108         if (DBG) log("stopCall: releaseSocket=" + releaseSocket);
   1109         releaseWifiHighPerfLock();
   1110         if (mAudioStream != null) {
   1111             mAudioStream.join(null);
   1112 
   1113             if (releaseSocket) {
   1114                 mAudioStream.release();
   1115                 mAudioStream = null;
   1116             }
   1117         }
   1118     }
   1119 
   1120     private String getLocalIp() {
   1121         return mSipSession.getLocalIp();
   1122     }
   1123 
   1124     private void throwSipException(Throwable throwable) throws SipException {
   1125         if (throwable instanceof SipException) {
   1126             throw (SipException) throwable;
   1127         } else {
   1128             throw new SipException("", throwable);
   1129         }
   1130     }
   1131 
   1132     private void log(String s) {
   1133         Rlog.d(LOG_TAG, s);
   1134     }
   1135 
   1136     private void loge(String s) {
   1137         Rlog.e(LOG_TAG, s);
   1138     }
   1139 
   1140     private void loge(String s, Throwable t) {
   1141         Rlog.e(LOG_TAG, s, t);
   1142     }
   1143 }
   1144