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.app.PendingIntent;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.os.IBinder;
     24 import android.os.Looper;
     25 import android.os.RemoteException;
     26 import android.os.ServiceManager;
     27 import android.util.Log;
     28 
     29 import java.text.ParseException;
     30 
     31 /**
     32  * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related
     33  * SIP services. This class is the starting point for any SIP actions. You can acquire an instance
     34  * of it with {@link #newInstance newInstance()}.</p>
     35  * <p>The APIs in this class allows you to:</p>
     36  * <ul>
     37  * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See
     38  * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li>
     39  * <li>Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may
     40  * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls
     41  * should be handled with a {@link SipAudioCall}, which you can acquire with {@link
     42  * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.</li>
     43  * <li>Register and unregister with a SIP service provider, with
     44  *      {@link #register register()} and {@link #unregister unregister()}.</li>
     45  * <li>Verify session connectivity, with {@link #isOpened isOpened()} and
     46  *      {@link #isRegistered isRegistered()}.</li>
     47  * </ul>
     48  * <p class="note"><strong>Note:</strong> Not all Android-powered devices support VOIP calls using
     49  * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported
     50  * isVoipSupported()} to verify that the device supports VOIP calling and {@link
     51  * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports
     52  * the SIP APIs. Your application must also request the {@link
     53  * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
     54  * permissions.</p>
     55  *
     56  * <div class="special reference">
     57  * <h3>Developer Guides</h3>
     58  * <p>For more information about using SIP, read the
     59  * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a>
     60  * developer guide.</p>
     61  * </div>
     62  */
     63 public class SipManager {
     64     /**
     65      * The result code to be sent back with the incoming call
     66      * {@link PendingIntent}.
     67      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     68      */
     69     public static final int INCOMING_CALL_RESULT_CODE = 101;
     70 
     71     /**
     72      * Key to retrieve the call ID from an incoming call intent.
     73      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     74      */
     75     public static final String EXTRA_CALL_ID = "android:sipCallID";
     76 
     77     /**
     78      * Key to retrieve the offered session description from an incoming call
     79      * intent.
     80      * @see #open(SipProfile, PendingIntent, SipRegistrationListener)
     81      */
     82     public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
     83 
     84     /**
     85      * Action to broadcast when SipService is up.
     86      * Internal use only.
     87      * @hide
     88      */
     89     public static final String ACTION_SIP_SERVICE_UP =
     90             "android.net.sip.SIP_SERVICE_UP";
     91     /**
     92      * Action string for the incoming call intent for the Phone app.
     93      * Internal use only.
     94      * @hide
     95      */
     96     public static final String ACTION_SIP_INCOMING_CALL =
     97             "com.android.phone.SIP_INCOMING_CALL";
     98     /**
     99      * Action string for the add-phone intent.
    100      * Internal use only.
    101      * @hide
    102      */
    103     public static final String ACTION_SIP_ADD_PHONE =
    104             "com.android.phone.SIP_ADD_PHONE";
    105     /**
    106      * Action string for the remove-phone intent.
    107      * Internal use only.
    108      * @hide
    109      */
    110     public static final String ACTION_SIP_REMOVE_PHONE =
    111             "com.android.phone.SIP_REMOVE_PHONE";
    112     /**
    113      * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
    114      * Internal use only.
    115      * @hide
    116      */
    117     public static final String EXTRA_LOCAL_URI = "android:localSipUri";
    118 
    119     private static final String TAG = "SipManager";
    120 
    121     private ISipService mSipService;
    122     private Context mContext;
    123 
    124     /**
    125      * Creates a manager instance. Returns null if SIP API is not supported.
    126      *
    127      * @param context application context for creating the manager object
    128      * @return the manager instance or null if SIP API is not supported
    129      */
    130     public static SipManager newInstance(Context context) {
    131         return (isApiSupported(context) ? new SipManager(context) : null);
    132     }
    133 
    134     /**
    135      * Returns true if the SIP API is supported by the system.
    136      */
    137     public static boolean isApiSupported(Context context) {
    138         return context.getPackageManager().hasSystemFeature(
    139                 PackageManager.FEATURE_SIP);
    140     }
    141 
    142     /**
    143      * Returns true if the system supports SIP-based VOIP API.
    144      */
    145     public static boolean isVoipSupported(Context context) {
    146         return context.getPackageManager().hasSystemFeature(
    147                 PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
    148     }
    149 
    150     /**
    151      * Returns true if SIP is only available on WIFI.
    152      */
    153     public static boolean isSipWifiOnly(Context context) {
    154         return context.getResources().getBoolean(
    155                 com.android.internal.R.bool.config_sip_wifi_only);
    156     }
    157 
    158     private SipManager(Context context) {
    159         mContext = context;
    160         createSipService();
    161     }
    162 
    163     private void createSipService() {
    164         IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
    165         mSipService = ISipService.Stub.asInterface(b);
    166     }
    167 
    168     /**
    169      * Opens the profile for making generic SIP calls. The caller may make subsequent calls
    170      * through {@link #makeAudioCall}. If one also wants to receive calls on the
    171      * profile, use
    172      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}
    173      * instead.
    174      *
    175      * @param localProfile the SIP profile to make calls from
    176      * @throws SipException if the profile contains incorrect settings or
    177      *      calling the SIP service results in an error
    178      */
    179     public void open(SipProfile localProfile) throws SipException {
    180         try {
    181             mSipService.open(localProfile);
    182         } catch (RemoteException e) {
    183             throw new SipException("open()", e);
    184         }
    185     }
    186 
    187     /**
    188      * Opens the profile for making calls and/or receiving generic SIP calls. The caller may
    189      * make subsequent calls through {@link #makeAudioCall}. If the
    190      * auto-registration option is enabled in the profile, the SIP service
    191      * will register the profile to the corresponding SIP provider periodically
    192      * in order to receive calls from the provider. When the SIP service
    193      * receives a new call, it will send out an intent with the provided action
    194      * string. The intent contains a call ID extra and an offer session
    195      * description string extra. Use {@link #getCallId} and
    196      * {@link #getOfferSessionDescription} to retrieve those extras.
    197      *
    198      * @param localProfile the SIP profile to receive incoming calls for
    199      * @param incomingCallPendingIntent When an incoming call is received, the
    200      *      SIP service will call
    201      *      {@link PendingIntent#send(Context, int, Intent)} to send back the
    202      *      intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the
    203      *      result code and the intent to fill in the call ID and session
    204      *      description information. It cannot be null.
    205      * @param listener to listen to registration events; can be null
    206      * @see #getCallId
    207      * @see #getOfferSessionDescription
    208      * @see #takeAudioCall
    209      * @throws NullPointerException if {@code incomingCallPendingIntent} is null
    210      * @throws SipException if the profile contains incorrect settings or
    211      *      calling the SIP service results in an error
    212      * @see #isIncomingCallIntent
    213      * @see #getCallId
    214      * @see #getOfferSessionDescription
    215      */
    216     public void open(SipProfile localProfile,
    217             PendingIntent incomingCallPendingIntent,
    218             SipRegistrationListener listener) throws SipException {
    219         if (incomingCallPendingIntent == null) {
    220             throw new NullPointerException(
    221                     "incomingCallPendingIntent cannot be null");
    222         }
    223         try {
    224             mSipService.open3(localProfile, incomingCallPendingIntent,
    225                     createRelay(listener, localProfile.getUriString()));
    226         } catch (RemoteException e) {
    227             throw new SipException("open()", e);
    228         }
    229     }
    230 
    231     /**
    232      * Sets the listener to listen to registration events. No effect if the
    233      * profile has not been opened to receive calls (see
    234      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}).
    235      *
    236      * @param localProfileUri the URI of the profile
    237      * @param listener to listen to registration events; can be null
    238      * @throws SipException if calling the SIP service results in an error
    239      */
    240     public void setRegistrationListener(String localProfileUri,
    241             SipRegistrationListener listener) throws SipException {
    242         try {
    243             mSipService.setRegistrationListener(
    244                     localProfileUri, createRelay(listener, localProfileUri));
    245         } catch (RemoteException e) {
    246             throw new SipException("setRegistrationListener()", e);
    247         }
    248     }
    249 
    250     /**
    251      * Closes the specified profile to not make/receive calls. All the resources
    252      * that were allocated to the profile are also released.
    253      *
    254      * @param localProfileUri the URI of the profile to close
    255      * @throws SipException if calling the SIP service results in an error
    256      */
    257     public void close(String localProfileUri) throws SipException {
    258         try {
    259             mSipService.close(localProfileUri);
    260         } catch (RemoteException e) {
    261             throw new SipException("close()", e);
    262         }
    263     }
    264 
    265     /**
    266      * Checks if the specified profile is opened in the SIP service for
    267      * making and/or receiving calls.
    268      *
    269      * @param localProfileUri the URI of the profile in question
    270      * @return true if the profile is enabled to receive calls
    271      * @throws SipException if calling the SIP service results in an error
    272      */
    273     public boolean isOpened(String localProfileUri) throws SipException {
    274         try {
    275             return mSipService.isOpened(localProfileUri);
    276         } catch (RemoteException e) {
    277             throw new SipException("isOpened()", e);
    278         }
    279     }
    280 
    281     /**
    282      * Checks if the SIP service has successfully registered the profile to the
    283      * SIP provider (specified in the profile) for receiving calls. Returning
    284      * true from this method also implies the profile is opened
    285      * ({@link #isOpened}).
    286      *
    287      * @param localProfileUri the URI of the profile in question
    288      * @return true if the profile is registered to the SIP provider; false if
    289      *        the profile has not been opened in the SIP service or the SIP
    290      *        service has not yet successfully registered the profile to the SIP
    291      *        provider
    292      * @throws SipException if calling the SIP service results in an error
    293      */
    294     public boolean isRegistered(String localProfileUri) throws SipException {
    295         try {
    296             return mSipService.isRegistered(localProfileUri);
    297         } catch (RemoteException e) {
    298             throw new SipException("isRegistered()", e);
    299         }
    300     }
    301 
    302     /**
    303      * Creates a {@link SipAudioCall} to make a call. The attempt will be timed
    304      * out if the call is not established within {@code timeout} seconds and
    305      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    306      * will be called.
    307      *
    308      * @param localProfile the SIP profile to make the call from
    309      * @param peerProfile the SIP profile to make the call to
    310      * @param listener to listen to the call events from {@link SipAudioCall};
    311      *      can be null
    312      * @param timeout the timeout value in seconds. Default value (defined by
    313      *        SIP protocol) is used if {@code timeout} is zero or negative.
    314      * @return a {@link SipAudioCall} object
    315      * @throws SipException if calling the SIP service results in an error or
    316      *      VOIP API is not supported by the device
    317      * @see SipAudioCall.Listener#onError
    318      * @see #isVoipSupported
    319      */
    320     public SipAudioCall makeAudioCall(SipProfile localProfile,
    321             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
    322             throws SipException {
    323         if (!isVoipSupported(mContext)) {
    324             throw new SipException("VOIP API is not supported");
    325         }
    326         SipAudioCall call = new SipAudioCall(mContext, localProfile);
    327         call.setListener(listener);
    328         SipSession s = createSipSession(localProfile, null);
    329         call.makeCall(peerProfile, s, timeout);
    330         return call;
    331     }
    332 
    333     /**
    334      * Creates a {@link SipAudioCall} to make an audio call. The attempt will be
    335      * timed out if the call is not established within {@code timeout} seconds
    336      * and
    337      * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
    338      * will be called.
    339      *
    340      * @param localProfileUri URI of the SIP profile to make the call from
    341      * @param peerProfileUri URI of the SIP profile to make the call to
    342      * @param listener to listen to the call events from {@link SipAudioCall};
    343      *      can be null
    344      * @param timeout the timeout value in seconds. Default value (defined by
    345      *        SIP protocol) is used if {@code timeout} is zero or negative.
    346      * @return a {@link SipAudioCall} object
    347      * @throws SipException if calling the SIP service results in an error or
    348      *      VOIP API is not supported by the device
    349      * @see SipAudioCall.Listener#onError
    350      * @see #isVoipSupported
    351      */
    352     public SipAudioCall makeAudioCall(String localProfileUri,
    353             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
    354             throws SipException {
    355         if (!isVoipSupported(mContext)) {
    356             throw new SipException("VOIP API is not supported");
    357         }
    358         try {
    359             return makeAudioCall(
    360                     new SipProfile.Builder(localProfileUri).build(),
    361                     new SipProfile.Builder(peerProfileUri).build(), listener,
    362                     timeout);
    363         } catch (ParseException e) {
    364             throw new SipException("build SipProfile", e);
    365         }
    366     }
    367 
    368     /**
    369      * Creates a {@link SipAudioCall} to take an incoming call. Before the call
    370      * is returned, the listener will receive a
    371      * {@link SipAudioCall.Listener#onRinging}
    372      * callback.
    373      *
    374      * @param incomingCallIntent the incoming call broadcast intent
    375      * @param listener to listen to the call events from {@link SipAudioCall};
    376      *      can be null
    377      * @return a {@link SipAudioCall} object
    378      * @throws SipException if calling the SIP service results in an error
    379      */
    380     public SipAudioCall takeAudioCall(Intent incomingCallIntent,
    381             SipAudioCall.Listener listener) throws SipException {
    382         if (incomingCallIntent == null) {
    383             throw new SipException("Cannot retrieve session with null intent");
    384         }
    385 
    386         String callId = getCallId(incomingCallIntent);
    387         if (callId == null) {
    388             throw new SipException("Call ID missing in incoming call intent");
    389         }
    390 
    391         String offerSd = getOfferSessionDescription(incomingCallIntent);
    392         if (offerSd == null) {
    393             throw new SipException("Session description missing in incoming "
    394                     + "call intent");
    395         }
    396 
    397         try {
    398             ISipSession session = mSipService.getPendingSession(callId);
    399             if (session == null) {
    400                 throw new SipException("No pending session for the call");
    401             }
    402             SipAudioCall call = new SipAudioCall(
    403                     mContext, session.getLocalProfile());
    404             call.attachCall(new SipSession(session), offerSd);
    405             call.setListener(listener);
    406             return call;
    407         } catch (Throwable t) {
    408             throw new SipException("takeAudioCall()", t);
    409         }
    410     }
    411 
    412     /**
    413      * Checks if the intent is an incoming call broadcast intent.
    414      *
    415      * @param intent the intent in question
    416      * @return true if the intent is an incoming call broadcast intent
    417      */
    418     public static boolean isIncomingCallIntent(Intent intent) {
    419         if (intent == null) return false;
    420         String callId = getCallId(intent);
    421         String offerSd = getOfferSessionDescription(intent);
    422         return ((callId != null) && (offerSd != null));
    423     }
    424 
    425     /**
    426      * Gets the call ID from the specified incoming call broadcast intent.
    427      *
    428      * @param incomingCallIntent the incoming call broadcast intent
    429      * @return the call ID or null if the intent does not contain it
    430      */
    431     public static String getCallId(Intent incomingCallIntent) {
    432         return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
    433     }
    434 
    435     /**
    436      * Gets the offer session description from the specified incoming call
    437      * broadcast intent.
    438      *
    439      * @param incomingCallIntent the incoming call broadcast intent
    440      * @return the offer session description or null if the intent does not
    441      *      have it
    442      */
    443     public static String getOfferSessionDescription(Intent incomingCallIntent) {
    444         return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
    445     }
    446 
    447     /**
    448      * Creates an incoming call broadcast intent.
    449      *
    450      * @param callId the call ID of the incoming call
    451      * @param sessionDescription the session description of the incoming call
    452      * @return the incoming call intent
    453      * @hide
    454      */
    455     public static Intent createIncomingCallBroadcast(String callId,
    456             String sessionDescription) {
    457         Intent intent = new Intent();
    458         intent.putExtra(EXTRA_CALL_ID, callId);
    459         intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
    460         return intent;
    461     }
    462 
    463     /**
    464      * Manually registers the profile to the corresponding SIP provider for
    465      * receiving calls.
    466      * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is
    467      * still needed to be called at least once in order for the SIP service to
    468      * notify the caller with the {@link android.app.PendingIntent} when an incoming call is
    469      * received.
    470      *
    471      * @param localProfile the SIP profile to register with
    472      * @param expiryTime registration expiration time (in seconds)
    473      * @param listener to listen to the registration events
    474      * @throws SipException if calling the SIP service results in an error
    475      */
    476     public void register(SipProfile localProfile, int expiryTime,
    477             SipRegistrationListener listener) throws SipException {
    478         try {
    479             ISipSession session = mSipService.createSession(localProfile,
    480                     createRelay(listener, localProfile.getUriString()));
    481             if (session == null) {
    482                 throw new SipException(
    483                         "SipService.createSession() returns null");
    484             }
    485             session.register(expiryTime);
    486         } catch (RemoteException e) {
    487             throw new SipException("register()", e);
    488         }
    489     }
    490 
    491     /**
    492      * Manually unregisters the profile from the corresponding SIP provider for
    493      * stop receiving further calls. This may interference with the auto
    494      * registration process in the SIP service if the auto-registration option
    495      * in the profile is enabled.
    496      *
    497      * @param localProfile the SIP profile to register with
    498      * @param listener to listen to the registration events
    499      * @throws SipException if calling the SIP service results in an error
    500      */
    501     public void unregister(SipProfile localProfile,
    502             SipRegistrationListener listener) throws SipException {
    503         try {
    504             ISipSession session = mSipService.createSession(localProfile,
    505                     createRelay(listener, localProfile.getUriString()));
    506             if (session == null) {
    507                 throw new SipException(
    508                         "SipService.createSession() returns null");
    509             }
    510             session.unregister();
    511         } catch (RemoteException e) {
    512             throw new SipException("unregister()", e);
    513         }
    514     }
    515 
    516     /**
    517      * Gets the {@link SipSession} that handles the incoming call. For audio
    518      * calls, consider to use {@link SipAudioCall} to handle the incoming call.
    519      * See {@link #takeAudioCall}. Note that the method may be called only once
    520      * for the same intent. For subsequent calls on the same intent, the method
    521      * returns null.
    522      *
    523      * @param incomingCallIntent the incoming call broadcast intent
    524      * @return the session object that handles the incoming call
    525      */
    526     public SipSession getSessionFor(Intent incomingCallIntent)
    527             throws SipException {
    528         try {
    529             String callId = getCallId(incomingCallIntent);
    530             ISipSession s = mSipService.getPendingSession(callId);
    531             return ((s == null) ? null : new SipSession(s));
    532         } catch (RemoteException e) {
    533             throw new SipException("getSessionFor()", e);
    534         }
    535     }
    536 
    537     private static ISipSessionListener createRelay(
    538             SipRegistrationListener listener, String uri) {
    539         return ((listener == null) ? null : new ListenerRelay(listener, uri));
    540     }
    541 
    542     /**
    543      * Creates a {@link SipSession} with the specified profile. Use other
    544      * methods, if applicable, instead of interacting with {@link SipSession}
    545      * directly.
    546      *
    547      * @param localProfile the SIP profile the session is associated with
    548      * @param listener to listen to SIP session events
    549      */
    550     public SipSession createSipSession(SipProfile localProfile,
    551             SipSession.Listener listener) throws SipException {
    552         try {
    553             ISipSession s = mSipService.createSession(localProfile, null);
    554             if (s == null) {
    555                 throw new SipException(
    556                         "Failed to create SipSession; network unavailable?");
    557             }
    558             return new SipSession(s, listener);
    559         } catch (RemoteException e) {
    560             throw new SipException("createSipSession()", e);
    561         }
    562     }
    563 
    564     /**
    565      * Gets the list of profiles hosted by the SIP service. The user information
    566      * (username, password and display name) are crossed out.
    567      * @hide
    568      */
    569     public SipProfile[] getListOfProfiles() {
    570         try {
    571             return mSipService.getListOfProfiles();
    572         } catch (RemoteException e) {
    573             return new SipProfile[0];
    574         }
    575     }
    576 
    577     private static class ListenerRelay extends SipSessionAdapter {
    578         private SipRegistrationListener mListener;
    579         private String mUri;
    580 
    581         // listener must not be null
    582         public ListenerRelay(SipRegistrationListener listener, String uri) {
    583             mListener = listener;
    584             mUri = uri;
    585         }
    586 
    587         private String getUri(ISipSession session) {
    588             try {
    589                 return ((session == null)
    590                         ? mUri
    591                         : session.getLocalProfile().getUriString());
    592             } catch (Throwable e) {
    593                 // SipService died? SIP stack died?
    594                 Log.w(TAG, "getUri(): " + e);
    595                 return null;
    596             }
    597         }
    598 
    599         @Override
    600         public void onRegistering(ISipSession session) {
    601             mListener.onRegistering(getUri(session));
    602         }
    603 
    604         @Override
    605         public void onRegistrationDone(ISipSession session, int duration) {
    606             long expiryTime = duration;
    607             if (duration > 0) expiryTime += System.currentTimeMillis();
    608             mListener.onRegistrationDone(getUri(session), expiryTime);
    609         }
    610 
    611         @Override
    612         public void onRegistrationFailed(ISipSession session, int errorCode,
    613                 String message) {
    614             mListener.onRegistrationFailed(getUri(session), errorCode, message);
    615         }
    616 
    617         @Override
    618         public void onRegistrationTimeout(ISipSession session) {
    619             mListener.onRegistrationFailed(getUri(session),
    620                     SipErrorCode.TIME_OUT, "registration timed out");
    621         }
    622     }
    623 }
    624