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