summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/stagefright/stagefright.cpp117
-rw-r--r--data/fonts/fonts.xml4
-rw-r--r--opengl/libs/EGL/egl.cpp11
-rw-r--r--opengl/libs/GLES2/gl2.cpp1
-rw-r--r--opengl/libs/GLES_CM/gl.cpp1
-rw-r--r--services/audioflinger/AudioFlinger.cpp89
-rw-r--r--services/java/com/android/server/sip/SipService.java25
-rw-r--r--services/java/com/android/server/sip/SipSessionGroup.java99
-rw-r--r--services/java/com/android/server/sip/SipSessionListenerProxy.java2
-rwxr-xr-xtelephony/java/com/android/internal/telephony/sip/SipPhone.java24
-rw-r--r--voip/java/android/net/sip/SdpSessionDescription.java428
-rw-r--r--voip/java/android/net/sip/SessionDescription.aidl19
-rw-r--r--voip/java/android/net/sip/SessionDescription.java83
-rw-r--r--voip/java/android/net/sip/SipAudioCall.java955
-rw-r--r--voip/java/android/net/sip/SipAudioCallImpl.java766
-rw-r--r--voip/java/android/net/sip/SipManager.java169
-rw-r--r--voip/java/android/net/sip/SipProfile.java5
-rw-r--r--voip/java/android/net/sip/SipSession.java531
-rw-r--r--voip/java/android/net/sip/SipSessionState.java94
19 files changed, 1694 insertions, 1729 deletions
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 8ab94ad..eb4b733 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+//#define LOG_NDEBUG 0
+#define LOG_TAG "stagefright"
+#include <media/stagefright/foundation/ADebug.h>
+
#include <sys/time.h>
#include <stdlib.h>
@@ -30,7 +34,6 @@
#include <media/stagefright/AudioPlayer.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/JPEGSource.h>
-#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaExtractor.h>
@@ -43,6 +46,8 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MPEG4Writer.h>
+#include <fcntl.h>
+
using namespace android;
static long gNumRepetitions;
@@ -120,7 +125,7 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) {
bool shouldSeek = false;
if (err == INFO_FORMAT_CHANGED) {
- CHECK_EQ(buffer, NULL);
+ CHECK(buffer == NULL);
printf("format changed.\n");
continue;
@@ -206,7 +211,7 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) {
options.clearSeekTo();
if (err != OK) {
- CHECK_EQ(buffer, NULL);
+ CHECK(buffer == NULL);
if (err == INFO_FORMAT_CHANGED) {
printf("format changed.\n");
@@ -267,14 +272,98 @@ static void playSource(OMXClient *client, sp<MediaSource> &source) {
}
}
-static void writeSourceToMP4(const sp<MediaSource> &source) {
+////////////////////////////////////////////////////////////////////////////////
+
+struct DetectSyncSource : public MediaSource {
+ DetectSyncSource(const sp<MediaSource> &source);
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options);
+
+private:
+ sp<MediaSource> mSource;
+ bool mIsAVC;
+
+ DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource);
+};
+
+DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source)
+ : mSource(source),
+ mIsAVC(false) {
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+status_t DetectSyncSource::start(MetaData *params) {
+ return mSource->start(params);
+}
+
+status_t DetectSyncSource::stop() {
+ return mSource->stop();
+}
+
+sp<MetaData> DetectSyncSource::getFormat() {
+ return mSource->getFormat();
+}
+
+static bool isIDRFrame(MediaBuffer *buffer) {
+ const uint8_t *data =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+ size_t size = buffer->range_length();
+ for (size_t i = 0; i + 3 < size; ++i) {
+ if (!memcmp("\x00\x00\x01", &data[i], 3)) {
+ uint8_t nalType = data[i + 3] & 0x1f;
+ if (nalType == 5) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+status_t DetectSyncSource::read(
+ MediaBuffer **buffer, const ReadOptions *options) {
+ status_t err = mSource->read(buffer, options);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (mIsAVC && isIDRFrame(*buffer)) {
+ (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
+ } else {
+ (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, false);
+ }
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void writeSourceToMP4(
+ sp<MediaSource> &source, bool syncInfoPresent) {
+ if (!syncInfoPresent) {
+ source = new DetectSyncSource(source);
+ }
+
sp<MPEG4Writer> writer =
new MPEG4Writer(gWriteMP4Filename.string());
- CHECK_EQ(writer->addSource(source), OK);
+ // at most one minute.
+ writer->setMaxFileDuration(60000000ll);
+
+ CHECK_EQ(writer->addSource(source), (status_t)OK);
sp<MetaData> params = new MetaData;
- CHECK_EQ(writer->start(), OK);
+ params->setInt32(kKeyNotRealTime, true);
+ CHECK_EQ(writer->start(params.get()), (status_t)OK);
while (!writer->reachedEOS()) {
usleep(100000);
@@ -283,7 +372,7 @@ static void writeSourceToMP4(const sp<MediaSource> &source) {
}
static void performSeekTest(const sp<MediaSource> &source) {
- CHECK_EQ(OK, source->start());
+ CHECK_EQ((status_t)OK, source->start());
int64_t durationUs;
CHECK(source->getFormat()->findInt64(kKeyDuration, &durationUs));
@@ -335,7 +424,7 @@ static void performSeekTest(const sp<MediaSource> &source) {
}
}
- CHECK_EQ(OK, source->stop());
+ CHECK_EQ((status_t)OK, source->stop());
}
static void usage(const char *me) {
@@ -481,10 +570,10 @@ int main(int argc, char **argv) {
for (int k = 0; k < argc; ++k) {
const char *filename = argv[k];
- CHECK_EQ(retriever->setDataSource(filename), OK);
+ CHECK_EQ(retriever->setDataSource(filename), (status_t)OK);
CHECK_EQ(retriever->setMode(
METADATA_MODE_FRAME_CAPTURE_AND_METADATA_RETRIEVAL),
- OK);
+ (status_t)OK);
sp<IMemory> mem = retriever->captureFrame();
@@ -530,7 +619,7 @@ int main(int argc, char **argv) {
Vector<CodecCapabilities> results;
CHECK_EQ(QueryCodecs(omx, kMimeTypes[k],
true, // queryDecoders
- &results), OK);
+ &results), (status_t)OK);
for (size_t i = 0; i < results.size(); ++i) {
printf(" decoder '%s' supports ",
@@ -579,6 +668,8 @@ int main(int argc, char **argv) {
status_t err = client.connect();
for (int k = 0; k < argc; ++k) {
+ bool syncInfoPresent = true;
+
const char *filename = argv[k];
sp<DataSource> dataSource = DataSource::CreateFromURI(filename);
@@ -625,6 +716,8 @@ int main(int argc, char **argv) {
}
extractor = rtspController.get();
+
+ syncInfoPresent = false;
} else {
extractor = MediaExtractor::Create(dataSource);
if (extractor == NULL) {
@@ -674,7 +767,7 @@ int main(int argc, char **argv) {
}
if (gWriteMP4) {
- writeSourceToMP4(mediaSource);
+ writeSourceToMP4(mediaSource, syncInfoPresent);
} else if (seekTest) {
performSeekTest(mediaSource);
} else {
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 6bcaaaf..1fd7bba 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -44,5 +44,5 @@
<name>monaco</name>
</font>
<fallback ttf="DroidSansFallback" />
- <fallback ttf="DroidSansJapanese" />
-</fonts> \ No newline at end of file
+ <fallback ttf="MTLmr3m" />
+</fonts>
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 0437263..2d1a278 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -428,19 +428,14 @@ static void(*findProcAddress(const char* name,
// ----------------------------------------------------------------------------
-static void gl_no_context() {
+static int gl_no_context() {
tls_t* tls = getTLS();
if (tls->logCallWithNoContext == EGL_TRUE) {
tls->logCallWithNoContext = EGL_FALSE;
LOGE("call to OpenGL ES API with no current context "
"(logged once per thread)");
}
-}
-
-// Always return GL_INVALID_OPERATION from glGetError() when called from
-// a thread without a bound context.
-static GLenum gl_no_context_glGetError() {
- return GL_INVALID_OPERATION;
+ return 0;
}
static void early_egl_init(void)
@@ -454,8 +449,6 @@ static void early_egl_init(void)
addr,
sizeof(gHooksNoContext));
- gHooksNoContext.gl.glGetError = gl_no_context_glGetError;
-
setGlThreadSpecific(&gHooksNoContext);
}
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 924737e..18dd483 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -58,6 +58,7 @@ using namespace android;
"ldr r12, [r12, %[tls]] \n" \
"cmp r12, #0 \n" \
"ldrne pc, [r12, %[api]] \n" \
+ "mov r0, #0 \n" \
"bx lr \n" \
: \
: [tls] "J"(TLS_SLOT_OPENGL_API*4), \
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index d71ff76..ee29f12 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -114,6 +114,7 @@ GL_API void GL_APIENTRY glWeightPointerOESBounds(GLint size, GLenum type,
"ldr r12, [r12, %[tls]] \n" \
"cmp r12, #0 \n" \
"ldrne pc, [r12, %[api]] \n" \
+ "mov r0, #0 \n" \
"bx lr \n" \
: \
: [tls] "J"(TLS_SLOT_OPENGL_API*4), \
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 56de765..8a732ed 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -4653,29 +4653,44 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid,
goto Exit;
}
- {
- Mutex::Autolock _l(mLock);
+ // check audio settings permission for global effects
+ if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) {
+ lStatus = PERMISSION_DENIED;
+ goto Exit;
+ }
- // check audio settings permission for global effects
- if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) {
- lStatus = PERMISSION_DENIED;
- goto Exit;
- }
+ // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects
+ // that can only be created by audio policy manager (running in same process)
+ if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) {
+ lStatus = PERMISSION_DENIED;
+ goto Exit;
+ }
- // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects
- // that can only be created by audio policy manager (running in same process)
- if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) {
- lStatus = PERMISSION_DENIED;
- goto Exit;
- }
+ // check recording permission for visualizer
+ if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 ||
+ memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) &&
+ !recordingAllowed()) {
+ lStatus = PERMISSION_DENIED;
+ goto Exit;
+ }
- // check recording permission for visualizer
- if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 ||
- memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) &&
- !recordingAllowed()) {
- lStatus = PERMISSION_DENIED;
+ if (output == 0) {
+ if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) {
+ // output must be specified by AudioPolicyManager when using session
+ // AudioSystem::SESSION_OUTPUT_STAGE
+ lStatus = BAD_VALUE;
goto Exit;
+ } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) {
+ // if the output returned by getOutputForEffect() is removed before we lock the
+ // mutex below, the call to checkPlaybackThread_l(output) below will detect it
+ // and we will exit safely
+ output = AudioSystem::getOutputForEffect(&desc);
}
+ }
+
+ {
+ Mutex::Autolock _l(mLock);
+
if (!EffectIsNullUuid(&pDesc->uuid)) {
// if uuid is specified, request effect descriptor
@@ -4744,32 +4759,24 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid,
// If output is not specified try to find a matching audio session ID in one of the
// output threads.
- // TODO: allow attachment of effect to inputs
+ // If output is 0 here, sessionId is neither SESSION_OUTPUT_STAGE nor SESSION_OUTPUT_MIX
+ // because of code checking output when entering the function.
if (output == 0) {
- if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) {
- // output must be specified by AudioPolicyManager when using session
- // AudioSystem::SESSION_OUTPUT_STAGE
- lStatus = BAD_VALUE;
- goto Exit;
- } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) {
- output = AudioSystem::getOutputForEffect(&desc);
- LOGV("createEffect() got output %d for effect %s", output, desc.name);
- } else {
- // look for the thread where the specified audio session is present
- for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
- if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
- output = mPlaybackThreads.keyAt(i);
- break;
- }
- }
- // If no output thread contains the requested session ID, default to
- // first output. The effect chain will be moved to the correct output
- // thread when a track with the same session ID is created
- if (output == 0 && mPlaybackThreads.size()) {
- output = mPlaybackThreads.keyAt(0);
+ // look for the thread where the specified audio session is present
+ for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
+ if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
+ output = mPlaybackThreads.keyAt(i);
+ break;
}
}
+ // If no output thread contains the requested session ID, default to
+ // first output. The effect chain will be moved to the correct output
+ // thread when a track with the same session ID is created
+ if (output == 0 && mPlaybackThreads.size()) {
+ output = mPlaybackThreads.keyAt(0);
+ }
}
+ LOGV("createEffect() got output %d for effect %s", output, desc.name);
PlaybackThread *thread = checkPlaybackThread_l(output);
if (thread == NULL) {
LOGE("createEffect() unknown output thread");
@@ -4777,6 +4784,8 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid,
goto Exit;
}
+ // TODO: allow attachment of effect to inputs
+
wclient = mClients.valueFor(pid);
if (wclient != NULL) {
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index f1dcd5a..3f43e1c 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -30,8 +30,8 @@ import android.net.sip.ISipSessionListener;
import android.net.sip.SipErrorCode;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
+import android.net.sip.SipSession;
import android.net.sip.SipSessionAdapter;
-import android.net.sip.SipSessionState;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
@@ -143,7 +143,7 @@ public final class SipService extends ISipService.Stub {
}
private void openToReceiveCalls(SipProfile localProfile) {
- open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null);
+ open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null);
}
public synchronized void open3(SipProfile localProfile,
@@ -255,15 +255,15 @@ public final class SipService extends ISipService.Stub {
private void notifyProfileAdded(SipProfile localProfile) {
if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
- Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
- intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+ Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
+ intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
mContext.sendBroadcast(intent);
}
private void notifyProfileRemoved(SipProfile localProfile) {
if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
- Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
- intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+ Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
+ intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
mContext.sendBroadcast(intent);
}
@@ -474,8 +474,8 @@ public final class SipService extends ISipService.Stub {
// send out incoming call broadcast
addPendingSession(session);
Intent intent = SipManager.createIncomingCallBroadcast(
- mIncomingCallBroadcastAction, session.getCallId(),
- sessionDescription);
+ session.getCallId(), sessionDescription)
+ .setAction(mIncomingCallBroadcastAction);
if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
+ caller.getUri() + ": " + session.getCallId()
+ " " + mIncomingCallBroadcastAction);
@@ -613,10 +613,10 @@ public final class SipService extends ISipService.Stub {
try {
int state = (mSession == null)
- ? SipSessionState.READY_TO_CALL
+ ? SipSession.State.READY_TO_CALL
: mSession.getState();
- if ((state == SipSessionState.REGISTERING)
- || (state == SipSessionState.DEREGISTERING)) {
+ if ((state == SipSession.State.REGISTERING)
+ || (state == SipSession.State.DEREGISTERING)) {
mProxy.onRegistering(mSession);
} else if (mRegistered) {
int duration = (int)
@@ -1138,7 +1138,8 @@ public final class SipService extends ISipService.Stub {
event.mTriggerTime += event.mPeriod;
// run the callback in a new thread to prevent deadlock
- new Thread(event.mCallback).start();
+ new Thread(event.mCallback, "SipServiceTimerCallbackThread")
+ .start();
}
if (DEBUG_TIMER) {
Log.d(TAG, "after timeout execution");
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index 03a076c..66a2c05 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -25,11 +25,10 @@ import gov.nist.javax.sip.message.SIPMessage;
import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener;
-import android.net.sip.SessionDescription;
import android.net.sip.SipErrorCode;
import android.net.sip.SipProfile;
+import android.net.sip.SipSession;
import android.net.sip.SipSessionAdapter;
-import android.net.sip.SipSessionState;
import android.text.TextUtils;
import android.util.Log;
@@ -121,7 +120,7 @@ class SipSessionGroup implements SipListener {
reset(localIp);
}
- void reset(String localIp) throws SipException, IOException {
+ synchronized void reset(String localIp) throws SipException, IOException {
mLocalIp = localIp;
if (localIp == null) return;
@@ -301,7 +300,7 @@ class SipSessionGroup implements SipListener {
boolean processed = (session != null) && session.process(event);
if (isLoggable && processed) {
Log.d(TAG, "new state after: "
- + SipSessionState.toString(session.mState));
+ + SipSession.State.toString(session.mState));
}
} catch (Throwable e) {
Log.w(TAG, "event process error: " + event, e);
@@ -332,7 +331,7 @@ class SipSessionGroup implements SipListener {
public boolean process(EventObject evt) throws SipException {
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
- + SipSessionState.toString(mState) + ": processing "
+ + SipSession.State.toString(mState) + ": processing "
+ log(evt));
if (isRequestEvent(Request.INVITE, evt)) {
RequestEvent event = (RequestEvent) evt;
@@ -342,7 +341,7 @@ class SipSessionGroup implements SipListener {
newSession.mDialog = newSession.mServerTransaction.getDialog();
newSession.mInviteReceived = event;
newSession.mPeerProfile = createPeerProfile(event.getRequest());
- newSession.mState = SipSessionState.INCOMING_CALL;
+ newSession.mState = SipSession.State.INCOMING_CALL;
newSession.mPeerSessionDescription =
extractContent(event.getRequest());
addSipSession(newSession);
@@ -361,7 +360,7 @@ class SipSessionGroup implements SipListener {
class SipSessionImpl extends ISipSession.Stub {
SipProfile mPeerProfile;
SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
- int mState = SipSessionState.READY_TO_CALL;
+ int mState = SipSession.State.READY_TO_CALL;
RequestEvent mInviteReceived;
Dialog mDialog;
ServerTransaction mServerTransaction;
@@ -381,7 +380,7 @@ class SipSessionGroup implements SipListener {
sleep(timeout);
if (mRunning) timeout();
}
- }).start();
+ }, "SipSessionTimerThread").start();
}
synchronized void cancel() {
@@ -416,7 +415,7 @@ class SipSessionGroup implements SipListener {
mInCall = false;
removeSipSession(this);
mPeerProfile = null;
- mState = SipSessionState.READY_TO_CALL;
+ mState = SipSession.State.READY_TO_CALL;
mInviteReceived = null;
mDialog = null;
mServerTransaction = null;
@@ -473,7 +472,7 @@ class SipSessionGroup implements SipListener {
onError(e);
}
}
- }).start();
+ }, "SipSessionAsyncCmdThread").start();
}
public void makeCall(SipProfile peerProfile, String sessionDescription,
@@ -523,10 +522,10 @@ class SipSessionGroup implements SipListener {
}
public void sendKeepAlive() {
- mState = SipSessionState.PINGING;
+ mState = SipSession.State.PINGING;
try {
processCommand(new OptionsCommand());
- while (SipSessionState.PINGING == mState) {
+ while (SipSession.State.PINGING == mState) {
Thread.sleep(1000);
}
} catch (SipException e) {
@@ -553,7 +552,7 @@ class SipSessionGroup implements SipListener {
try {
String s = super.toString();
return s.substring(s.indexOf("@")) + ":"
- + SipSessionState.toString(mState);
+ + SipSession.State.toString(mState);
} catch (Throwable e) {
return super.toString();
}
@@ -561,7 +560,7 @@ class SipSessionGroup implements SipListener {
public boolean process(EventObject evt) throws SipException {
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
- + SipSessionState.toString(mState) + ": processing "
+ + SipSession.State.toString(mState) + ": processing "
+ log(evt));
synchronized (SipSessionGroup.this) {
if (isClosed()) return false;
@@ -577,30 +576,30 @@ class SipSessionGroup implements SipListener {
boolean processed;
switch (mState) {
- case SipSessionState.REGISTERING:
- case SipSessionState.DEREGISTERING:
+ case SipSession.State.REGISTERING:
+ case SipSession.State.DEREGISTERING:
processed = registeringToReady(evt);
break;
- case SipSessionState.PINGING:
+ case SipSession.State.PINGING:
processed = keepAliveProcess(evt);
break;
- case SipSessionState.READY_TO_CALL:
+ case SipSession.State.READY_TO_CALL:
processed = readyForCall(evt);
break;
- case SipSessionState.INCOMING_CALL:
+ case SipSession.State.INCOMING_CALL:
processed = incomingCall(evt);
break;
- case SipSessionState.INCOMING_CALL_ANSWERING:
+ case SipSession.State.INCOMING_CALL_ANSWERING:
processed = incomingCallToInCall(evt);
break;
- case SipSessionState.OUTGOING_CALL:
- case SipSessionState.OUTGOING_CALL_RING_BACK:
+ case SipSession.State.OUTGOING_CALL:
+ case SipSession.State.OUTGOING_CALL_RING_BACK:
processed = outgoingCall(evt);
break;
- case SipSessionState.OUTGOING_CALL_CANCELING:
+ case SipSession.State.OUTGOING_CALL_CANCELING:
processed = outgoingCallToReady(evt);
break;
- case SipSessionState.IN_CALL:
+ case SipSession.State.IN_CALL:
processed = inCall(evt);
break;
default:
@@ -650,8 +649,8 @@ class SipSessionGroup implements SipListener {
private void processTransactionTerminated(
TransactionTerminatedEvent event) {
switch (mState) {
- case SipSessionState.IN_CALL:
- case SipSessionState.READY_TO_CALL:
+ case SipSession.State.IN_CALL:
+ case SipSession.State.READY_TO_CALL:
Log.d(TAG, "Transaction terminated; do nothing");
break;
default:
@@ -670,27 +669,27 @@ class SipSessionGroup implements SipListener {
? event.getServerTransaction()
: event.getClientTransaction();
- if ((current != target) && (mState != SipSessionState.PINGING)) {
+ if ((current != target) && (mState != SipSession.State.PINGING)) {
Log.d(TAG, "not the current transaction; current=" + current
+ ", timed out=" + target);
return;
}
switch (mState) {
- case SipSessionState.REGISTERING:
- case SipSessionState.DEREGISTERING:
+ case SipSession.State.REGISTERING:
+ case SipSession.State.DEREGISTERING:
reset();
mProxy.onRegistrationTimeout(this);
break;
- case SipSessionState.INCOMING_CALL:
- case SipSessionState.INCOMING_CALL_ANSWERING:
- case SipSessionState.OUTGOING_CALL:
- case SipSessionState.OUTGOING_CALL_CANCELING:
+ case SipSession.State.INCOMING_CALL:
+ case SipSession.State.INCOMING_CALL_ANSWERING:
+ case SipSession.State.OUTGOING_CALL:
+ case SipSession.State.OUTGOING_CALL_CANCELING:
onError(SipErrorCode.TIME_OUT, event.toString());
break;
- case SipSessionState.PINGING:
+ case SipSession.State.PINGING:
reset();
mReRegisterFlag = true;
- mState = SipSessionState.READY_TO_CALL;
+ mState = SipSession.State.READY_TO_CALL;
break;
default:
@@ -764,7 +763,7 @@ class SipSessionGroup implements SipListener {
switch (statusCode) {
case Response.OK:
int state = mState;
- onRegistrationDone((state == SipSessionState.REGISTERING)
+ onRegistrationDone((state == SipSession.State.REGISTERING)
? getExpiryTime(((ResponseEvent) evt).getResponse())
: -1);
mLastNonce = null;
@@ -851,7 +850,7 @@ class SipSessionGroup implements SipListener {
generateTag());
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSessionState.OUTGOING_CALL;
+ mState = SipSession.State.OUTGOING_CALL;
mProxy.onCalling(this);
startSessionTimer(cmd.getTimeout());
return true;
@@ -861,7 +860,7 @@ class SipSessionGroup implements SipListener {
generateTag(), duration);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSessionState.REGISTERING;
+ mState = SipSession.State.REGISTERING;
mProxy.onRegistering(this);
return true;
} else if (DEREGISTER == evt) {
@@ -869,7 +868,7 @@ class SipSessionGroup implements SipListener {
generateTag(), 0);
mDialog = mClientTransaction.getDialog();
addSipSession(this);
- mState = SipSessionState.DEREGISTERING;
+ mState = SipSession.State.DEREGISTERING;
mProxy.onRegistering(this);
return true;
}
@@ -884,7 +883,7 @@ class SipSessionGroup implements SipListener {
mLocalProfile,
((MakeCallCommand) evt).getSessionDescription(),
mServerTransaction);
- mState = SipSessionState.INCOMING_CALL_ANSWERING;
+ mState = SipSession.State.INCOMING_CALL_ANSWERING;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
} else if (END_CALL == evt) {
@@ -925,8 +924,8 @@ class SipSessionGroup implements SipListener {
int statusCode = response.getStatusCode();
switch (statusCode) {
case Response.RINGING:
- if (mState == SipSessionState.OUTGOING_CALL) {
- mState = SipSessionState.OUTGOING_CALL_RING_BACK;
+ if (mState == SipSession.State.OUTGOING_CALL) {
+ mState = SipSession.State.OUTGOING_CALL_RING_BACK;
mProxy.onRingingBack(this);
cancelSessionTimer();
}
@@ -969,7 +968,7 @@ class SipSessionGroup implements SipListener {
// response comes back yet. We are cheating for not checking
// response.
mSipHelper.sendCancel(mClientTransaction);
- mState = SipSessionState.OUTGOING_CALL_CANCELING;
+ mState = SipSession.State.OUTGOING_CALL_CANCELING;
startSessionTimer(CANCEL_CALL_TIMER);
return true;
}
@@ -1025,7 +1024,7 @@ class SipSessionGroup implements SipListener {
} else if (isRequestEvent(Request.INVITE, evt)) {
// got Re-INVITE
RequestEvent event = mInviteReceived = (RequestEvent) evt;
- mState = SipSessionState.INCOMING_CALL;
+ mState = SipSession.State.INCOMING_CALL;
mPeerSessionDescription = extractContent(event.getRequest());
mServerTransaction = null;
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1038,7 +1037,7 @@ class SipSessionGroup implements SipListener {
// to change call
mClientTransaction = mSipHelper.sendReinvite(mDialog,
((MakeCallCommand) evt).getSessionDescription());
- mState = SipSessionState.OUTGOING_CALL;
+ mState = SipSession.State.OUTGOING_CALL;
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
}
@@ -1066,14 +1065,14 @@ class SipSessionGroup implements SipListener {
}
private void establishCall() {
- mState = SipSessionState.IN_CALL;
+ mState = SipSession.State.IN_CALL;
mInCall = true;
cancelSessionTimer();
mProxy.onCallEstablished(this, mPeerSessionDescription);
}
private void fallbackToPreviousInCall(int errorCode, String message) {
- mState = SipSessionState.IN_CALL;
+ mState = SipSession.State.IN_CALL;
mProxy.onCallChangeFailed(this, errorCode, message);
}
@@ -1095,8 +1094,8 @@ class SipSessionGroup implements SipListener {
private void onError(int errorCode, String message) {
cancelSessionTimer();
switch (mState) {
- case SipSessionState.REGISTERING:
- case SipSessionState.DEREGISTERING:
+ case SipSession.State.REGISTERING:
+ case SipSession.State.DEREGISTERING:
onRegistrationFailed(errorCode, message);
break;
default:
@@ -1270,7 +1269,7 @@ class SipSessionGroup implements SipListener {
private static boolean isLoggable(SipSessionImpl s) {
if (s != null) {
switch (s.mState) {
- case SipSessionState.PINGING:
+ case SipSession.State.PINGING:
return DEBUG_PING;
}
}
diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java
index a4cd102..f8be0a8 100644
--- a/services/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -40,7 +40,7 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub {
// One thread for each calling back.
// Note: Guarantee ordering if the issue becomes important. Currently,
// the chance of handling two callback events at a time is none.
- new Thread(runnable).start();
+ new Thread(runnable, "SipSessionCallbackThread").start();
}
public void onCalling(final ISipSession session) {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 35aa3b3..372acc2 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -27,7 +27,7 @@ import android.net.sip.SipErrorCode;
import android.net.sip.SipException;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
-import android.net.sip.SipSessionState;
+import android.net.sip.SipSession;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
@@ -92,7 +92,7 @@ public class SipPhone extends SipPhoneBase {
foregroundCall = new SipCall();
backgroundCall = new SipCall();
mProfile = profile;
- mSipManager = SipManager.getInstance(context);
+ mSipManager = SipManager.newInstance(context);
// FIXME: what's this for SIP?
//Change the system property
@@ -707,8 +707,8 @@ public class SipPhone extends SipPhoneBase {
void dial() throws SipException {
setState(Call.State.DIALING);
- mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
- mPeer, null, SESSION_TIMEOUT);
+ mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
+ SESSION_TIMEOUT);
mSipAudioCall.setRingbackToneEnabled(false);
mSipAudioCall.setListener(mAdapter);
}
@@ -808,20 +808,20 @@ public class SipPhone extends SipPhoneBase {
if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
int sessionState = sipAudioCall.getState();
switch (sessionState) {
- case SipSessionState.READY_TO_CALL: return Call.State.IDLE;
- case SipSessionState.INCOMING_CALL:
- case SipSessionState.INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
- case SipSessionState.OUTGOING_CALL: return Call.State.DIALING;
- case SipSessionState.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
- case SipSessionState.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
- case SipSessionState.IN_CALL: return Call.State.ACTIVE;
+ case SipSession.State.READY_TO_CALL: return Call.State.IDLE;
+ case SipSession.State.INCOMING_CALL:
+ case SipSession.State.INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
+ case SipSession.State.OUTGOING_CALL: return Call.State.DIALING;
+ case SipSession.State.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
+ case SipSession.State.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
+ case SipSession.State.IN_CALL: return Call.State.ACTIVE;
default:
Log.w(LOG_TAG, "illegal connection state: " + sessionState);
return Call.State.DISCONNECTED;
}
}
- private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
+ private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
protected abstract void onCallEnded(Connection.DisconnectCause cause);
protected abstract void onError(Connection.DisconnectCause cause);
diff --git a/voip/java/android/net/sip/SdpSessionDescription.java b/voip/java/android/net/sip/SdpSessionDescription.java
deleted file mode 100644
index f6ae837..0000000
--- a/voip/java/android/net/sip/SdpSessionDescription.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import gov.nist.javax.sdp.SessionDescriptionImpl;
-import gov.nist.javax.sdp.fields.AttributeField;
-import gov.nist.javax.sdp.fields.ConnectionField;
-import gov.nist.javax.sdp.fields.MediaField;
-import gov.nist.javax.sdp.fields.OriginField;
-import gov.nist.javax.sdp.fields.ProtoVersionField;
-import gov.nist.javax.sdp.fields.SessionNameField;
-import gov.nist.javax.sdp.fields.TimeField;
-import gov.nist.javax.sdp.parser.SDPAnnounceParser;
-
-import android.util.Log;
-
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Vector;
-import javax.sdp.Connection;
-import javax.sdp.MediaDescription;
-import javax.sdp.SdpException;
-
-/**
- * A session description that follows SDP (Session Description Protocol).
- * Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>.
- * @hide
- */
-public class SdpSessionDescription extends SessionDescription {
- private static final String TAG = "SDP";
- private static final String AUDIO = "audio";
- private static final String RTPMAP = "rtpmap";
- private static final String PTIME = "ptime";
- private static final String SENDONLY = "sendonly";
- private static final String RECVONLY = "recvonly";
- private static final String INACTIVE = "inactive";
-
- private SessionDescriptionImpl mSessionDescription;
-
- /**
- * The audio codec information parsed from "rtpmap".
- */
- public static class AudioCodec {
- public final int payloadType;
- public final String name;
- public final int sampleRate;
- public final int sampleCount;
-
- public AudioCodec(int payloadType, String name, int sampleRate,
- int sampleCount) {
- this.payloadType = payloadType;
- this.name = name;
- this.sampleRate = sampleRate;
- this.sampleCount = sampleCount;
- }
- }
-
- /**
- * The builder class used to create an {@link SdpSessionDescription} object.
- */
- public static class Builder {
- private SdpSessionDescription mSdp = new SdpSessionDescription();
- private SessionDescriptionImpl mSessionDescription;
-
- public Builder(String sessionName) throws SdpException {
- mSessionDescription = new SessionDescriptionImpl();
- mSdp.mSessionDescription = mSessionDescription;
- try {
- ProtoVersionField proto = new ProtoVersionField();
- proto.setVersion(0);
- mSessionDescription.addField(proto);
-
- TimeField time = new TimeField();
- time.setZero();
- mSessionDescription.addField(time);
-
- SessionNameField session = new SessionNameField();
- session.setValue(sessionName);
- mSessionDescription.addField(session);
- } catch (Exception e) {
- throwSdpException(e);
- }
- }
-
- public Builder setConnectionInfo(String networkType, String addressType,
- String addr) throws SdpException {
- try {
- ConnectionField connection = new ConnectionField();
- connection.setNetworkType(networkType);
- connection.setAddressType(addressType);
- connection.setAddress(addr);
- mSessionDescription.addField(connection);
- } catch (Exception e) {
- throwSdpException(e);
- }
- return this;
- }
-
- public Builder setOrigin(SipProfile user, long sessionId,
- long sessionVersion, String networkType, String addressType,
- String address) throws SdpException {
- try {
- OriginField origin = new OriginField();
- origin.setUsername(user.getUserName());
- origin.setSessionId(sessionId);
- origin.setSessionVersion(sessionVersion);
- origin.setAddressType(addressType);
- origin.setNetworkType(networkType);
- origin.setAddress(address);
- mSessionDescription.addField(origin);
- } catch (Exception e) {
- throwSdpException(e);
- }
- return this;
- }
-
- public Builder addMedia(String media, int port, int numPorts,
- String transport, Integer... types) throws SdpException {
- MediaField field = new MediaField();
- Vector<Integer> typeVector = new Vector<Integer>();
- Collections.addAll(typeVector, types);
- try {
- field.setMediaType(media);
- field.setMediaPort(port);
- field.setPortCount(numPorts);
- field.setProtocol(transport);
- field.setMediaFormats(typeVector);
- mSessionDescription.addField(field);
- } catch (Exception e) {
- throwSdpException(e);
- }
- return this;
- }
-
- public Builder addMediaAttribute(String type, String name, String value)
- throws SdpException {
- try {
- MediaDescription md = mSdp.getMediaDescription(type);
- if (md == null) {
- throw new SdpException("Should add media first!");
- }
- AttributeField attribute = new AttributeField();
- attribute.setName(name);
- attribute.setValueAllowNull(value);
- mSessionDescription.addField(attribute);
- } catch (Exception e) {
- throwSdpException(e);
- }
- return this;
- }
-
- public Builder addSessionAttribute(String name, String value)
- throws SdpException {
- try {
- AttributeField attribute = new AttributeField();
- attribute.setName(name);
- attribute.setValueAllowNull(value);
- mSessionDescription.addField(attribute);
- } catch (Exception e) {
- throwSdpException(e);
- }
- return this;
- }
-
- private void throwSdpException(Exception e) throws SdpException {
- if (e instanceof SdpException) {
- throw (SdpException) e;
- } else {
- throw new SdpException(e.toString(), e);
- }
- }
-
- public String build() {
- return mSdp.toString();
- }
- }
-
- private SdpSessionDescription() {
- }
-
- /**
- * Constructor.
- *
- * @param sdpString an SDP session description to parse
- */
- public SdpSessionDescription(String sdpString) throws SdpException {
- try {
- mSessionDescription = new SDPAnnounceParser(sdpString).parse();
- } catch (ParseException e) {
- throw new SdpException(e.toString(), e);
- }
- verify();
- }
-
- /**
- * Constructor.
- *
- * @param content a raw SDP session description to parse
- */
- public SdpSessionDescription(byte[] content) throws SdpException {
- this(new String(content));
- }
-
- private void verify() throws SdpException {
- // make sure the syntax is correct over the fields we're interested in
- Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
- mSessionDescription.getMediaDescriptions(false);
- for (MediaDescription md : descriptions) {
- md.getMedia().getMediaPort();
- Connection connection = md.getConnection();
- if (connection != null) connection.getAddress();
- md.getMedia().getFormats();
- }
- Connection connection = mSessionDescription.getConnection();
- if (connection != null) connection.getAddress();
- }
-
- /**
- * Gets the connection address of the media.
- *
- * @param type the media type; e.g., "AUDIO"
- * @return the media connection address of the peer
- */
- public String getPeerMediaAddress(String type) {
- try {
- MediaDescription md = getMediaDescription(type);
- Connection connection = md.getConnection();
- if (connection == null) {
- connection = mSessionDescription.getConnection();
- }
- return ((connection == null) ? null : connection.getAddress());
- } catch (SdpException e) {
- // should not occur
- return null;
- }
- }
-
- /**
- * Gets the connection port number of the media.
- *
- * @param type the media type; e.g., "AUDIO"
- * @return the media connection port number of the peer
- */
- public int getPeerMediaPort(String type) {
- try {
- MediaDescription md = getMediaDescription(type);
- return md.getMedia().getMediaPort();
- } catch (SdpException e) {
- // should not occur
- return -1;
- }
- }
-
- private boolean containsAttribute(String type, String name) {
- if (name == null) return false;
- MediaDescription md = getMediaDescription(type);
- Vector<AttributeField> v = (Vector<AttributeField>)
- md.getAttributeFields();
- for (AttributeField field : v) {
- if (name.equals(field.getAttribute().getName())) return true;
- }
- return false;
- }
-
- /**
- * Checks if the media is "sendonly".
- *
- * @param type the media type; e.g., "AUDIO"
- * @return true if the media is "sendonly"
- */
- public boolean isSendOnly(String type) {
- boolean answer = containsAttribute(type, SENDONLY);
- Log.d(TAG, " sendonly? " + answer);
- return answer;
- }
-
- /**
- * Checks if the media is "recvonly".
- *
- * @param type the media type; e.g., "AUDIO"
- * @return true if the media is "recvonly"
- */
- public boolean isReceiveOnly(String type) {
- boolean answer = containsAttribute(type, RECVONLY);
- Log.d(TAG, " recvonly? " + answer);
- return answer;
- }
-
- /**
- * Checks if the media is in sending; i.e., not "recvonly" and not
- * "inactive".
- *
- * @param type the media type; e.g., "AUDIO"
- * @return true if the media is sending
- */
- public boolean isSending(String type) {
- boolean answer = !containsAttribute(type, RECVONLY)
- && !containsAttribute(type, INACTIVE);
-
- Log.d(TAG, " sending? " + answer);
- return answer;
- }
-
- /**
- * Checks if the media is in receiving; i.e., not "sendonly" and not
- * "inactive".
- *
- * @param type the media type; e.g., "AUDIO"
- * @return true if the media is receiving
- */
- public boolean isReceiving(String type) {
- boolean answer = !containsAttribute(type, SENDONLY)
- && !containsAttribute(type, INACTIVE);
- Log.d(TAG, " receiving? " + answer);
- return answer;
- }
-
- private AudioCodec parseAudioCodec(String rtpmap, int ptime) {
- String[] ss = rtpmap.split(" ");
- int payloadType = Integer.parseInt(ss[0]);
-
- ss = ss[1].split("/");
- String name = ss[0];
- int sampleRate = Integer.parseInt(ss[1]);
- int channelCount = 1;
- if (ss.length > 2) channelCount = Integer.parseInt(ss[2]);
- int sampleCount = sampleRate / (1000 / ptime) * channelCount;
- return new AudioCodec(payloadType, name, sampleRate, sampleCount);
- }
-
- /**
- * Gets the list of audio codecs in this session description.
- *
- * @return the list of audio codecs in this session description
- */
- public List<AudioCodec> getAudioCodecs() {
- MediaDescription md = getMediaDescription(AUDIO);
- if (md == null) return new ArrayList<AudioCodec>();
-
- // FIXME: what happens if ptime is missing
- int ptime = 20;
- try {
- String value = md.getAttribute(PTIME);
- if (value != null) ptime = Integer.parseInt(value);
- } catch (Throwable t) {
- Log.w(TAG, "getCodecs(): ignored: " + t);
- }
-
- List<AudioCodec> codecs = new ArrayList<AudioCodec>();
- Vector<AttributeField> v = (Vector<AttributeField>)
- md.getAttributeFields();
- for (AttributeField field : v) {
- try {
- if (RTPMAP.equals(field.getName())) {
- AudioCodec codec = parseAudioCodec(field.getValue(), ptime);
- if (codec != null) codecs.add(codec);
- }
- } catch (Throwable t) {
- Log.w(TAG, "getCodecs(): ignored: " + t);
- }
- }
- return codecs;
- }
-
- /**
- * Gets the media description of the specified type.
- *
- * @param type the media type; e.g., "AUDIO"
- * @return the media description of the specified type
- */
- public MediaDescription getMediaDescription(String type) {
- MediaDescription[] all = getMediaDescriptions();
- if ((all == null) || (all.length == 0)) return null;
- for (MediaDescription md : all) {
- String t = md.getMedia().getMedia();
- if (t.equalsIgnoreCase(type)) return md;
- }
- return null;
- }
-
- /**
- * Gets all the media descriptions in this session description.
- *
- * @return all the media descriptions in this session description
- */
- public MediaDescription[] getMediaDescriptions() {
- try {
- Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
- mSessionDescription.getMediaDescriptions(false);
- MediaDescription[] all = new MediaDescription[descriptions.size()];
- return descriptions.toArray(all);
- } catch (SdpException e) {
- Log.e(TAG, "getMediaDescriptions", e);
- }
- return null;
- }
-
- @Override
- public String getType() {
- return "sdp";
- }
-
- @Override
- public byte[] getContent() {
- return mSessionDescription.toString().getBytes();
- }
-
- @Override
- public String toString() {
- return mSessionDescription.toString();
- }
-}
diff --git a/voip/java/android/net/sip/SessionDescription.aidl b/voip/java/android/net/sip/SessionDescription.aidl
deleted file mode 100644
index a120d16..0000000
--- a/voip/java/android/net/sip/SessionDescription.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-parcelable SessionDescription;
diff --git a/voip/java/android/net/sip/SessionDescription.java b/voip/java/android/net/sip/SessionDescription.java
deleted file mode 100644
index d476f0b..0000000
--- a/voip/java/android/net/sip/SessionDescription.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Abstract class of a session description.
- * @hide
- */
-public abstract class SessionDescription implements Parcelable {
- /** @hide */
- public static final Parcelable.Creator<SessionDescription> CREATOR =
- new Parcelable.Creator<SessionDescription>() {
- public SessionDescription createFromParcel(Parcel in) {
- return new SessionDescriptionImpl(in);
- }
-
- public SessionDescription[] newArray(int size) {
- return new SessionDescriptionImpl[size];
- }
- };
-
- /**
- * Gets the type of the session description; e.g., "SDP".
- *
- * @return the session description type
- */
- public abstract String getType();
-
- /**
- * Gets the raw content of the session description.
- *
- * @return the content of the session description
- */
- public abstract byte[] getContent();
-
- /** @hide */
- public void writeToParcel(Parcel out, int flags) {
- out.writeString(getType());
- out.writeByteArray(getContent());
- }
-
- /** @hide */
- public int describeContents() {
- return 0;
- }
-
- private static class SessionDescriptionImpl extends SessionDescription {
- private String mType;
- private byte[] mContent;
-
- SessionDescriptionImpl(Parcel in) {
- mType = in.readString();
- mContent = in.createByteArray();
- }
-
- @Override
- public String getType() {
- return mType;
- }
-
- @Override
- public byte[] getContent() {
- return mContent;
- }
- }
-}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 0069fe0..2f4fd90 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -16,120 +16,184 @@
package android.net.sip;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.net.rtp.AudioCodec;
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
+import android.net.rtp.RtpStream;
+import android.net.sip.SimpleSessionDescription.Media;
+import android.net.wifi.WifiManager;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
- * Interface for making audio calls over SIP.
- * @hide
+ * Class that handles an audio call over SIP.
*/
-public interface SipAudioCall {
+/** @hide */
+public class SipAudioCall extends SipSessionAdapter {
+ private static final String TAG = SipAudioCall.class.getSimpleName();
+ private static final boolean RELEASE_SOCKET = true;
+ private static final boolean DONT_RELEASE_SOCKET = false;
+ private static final int SESSION_TIMEOUT = 5; // in seconds
+
/** Listener class for all event callbacks. */
- public interface Listener {
+ public static class Listener {
/**
* Called when the call object is ready to make another call.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that is ready to make another call
*/
- void onReadyToCall(SipAudioCall call);
+ public void onReadyToCall(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when a request is sent out to initiate a new call.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onCalling(SipAudioCall call);
+ public void onCalling(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when a new call comes in.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
* @param caller the SIP profile of the caller
*/
- void onRinging(SipAudioCall call, SipProfile caller);
+ public void onRinging(SipAudioCall call, SipProfile caller) {
+ onChanged(call);
+ }
/**
- * Called when a RINGING response is received for the INVITE request sent
+ * Called when a RINGING response is received for the INVITE request
+ * sent. The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onRingingBack(SipAudioCall call);
+ public void onRingingBack(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when the session is established.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onCallEstablished(SipAudioCall call);
+ public void onCallEstablished(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when the session is terminated.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onCallEnded(SipAudioCall call);
+ public void onCallEnded(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when the peer is busy during session initialization.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onCallBusy(SipAudioCall call);
+ public void onCallBusy(SipAudioCall call) {
+ onChanged(call);
+ }
/**
* Called when the call is on hold.
+ * The default implementation calls {@link #onChange}.
*
* @param call the call object that carries out the audio call
*/
- void onCallHeld(SipAudioCall call);
+ public void onCallHeld(SipAudioCall call) {
+ onChanged(call);
+ }
/**
- * Called when an error occurs.
+ * Called when an error occurs. The default implementation is no op.
*
* @param call the call object that carries out the audio call
* @param errorCode error code of this error
* @param errorMessage error message
* @see SipErrorCode
*/
- void onError(SipAudioCall call, int errorCode, String errorMessage);
+ public void onError(SipAudioCall call, int errorCode,
+ String errorMessage) {
+ // no-op
+ }
+
+ /**
+ * Called when an event occurs and the corresponding callback is not
+ * overridden. The default implementation is no op. Error events are
+ * not re-directed to this callback and are handled in {@link #onError}.
+ */
+ public void onChanged(SipAudioCall call) {
+ // no-op
+ }
}
+ private Context mContext;
+ private SipProfile mLocalProfile;
+ private SipAudioCall.Listener mListener;
+ private SipSession mSipSession;
+
+ private long mSessionId = System.currentTimeMillis();
+ private String mPeerSd;
+
+ private AudioStream mAudioStream;
+ private AudioGroup mAudioGroup;
+
+ private boolean mInCall = false;
+ private boolean mMuted = false;
+ private boolean mHold = false;
+
+ private boolean mRingbackToneEnabled = true;
+ private boolean mRingtoneEnabled = true;
+ private Ringtone mRingtone;
+ private ToneGenerator mRingbackTone;
+
+ private SipProfile mPendingCallRequest;
+ private WifiManager mWm;
+ private WifiManager.WifiLock mWifiHighPerfLock;
+
+ private int mErrorCode = SipErrorCode.NO_ERROR;
+ private String mErrorMessage;
+
/**
- * The adapter class for {@link Listener}. The default implementation of
- * all callback methods is no-op.
+ * Creates a call object with the local SIP profile.
+ * @param context the context for accessing system services such as
+ * ringtone, audio, WIFI etc
*/
- public class Adapter implements Listener {
- protected void onChanged(SipAudioCall call) {
- }
- public void onReadyToCall(SipAudioCall call) {
- onChanged(call);
- }
- public void onCalling(SipAudioCall call) {
- onChanged(call);
- }
- public void onRinging(SipAudioCall call, SipProfile caller) {
- onChanged(call);
- }
- public void onRingingBack(SipAudioCall call) {
- onChanged(call);
- }
- public void onCallEstablished(SipAudioCall call) {
- onChanged(call);
- }
- public void onCallEnded(SipAudioCall call) {
- onChanged(call);
- }
- public void onCallBusy(SipAudioCall call) {
- onChanged(call);
- }
- public void onCallHeld(SipAudioCall call) {
- onChanged(call);
- }
- public void onError(SipAudioCall call, int errorCode,
- String errorMessage) {
- onChanged(call);
- }
+ public SipAudioCall(Context context, SipProfile localProfile) {
+ mContext = context;
+ mLocalProfile = localProfile;
+ mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
/**
@@ -139,7 +203,9 @@ public interface SipAudioCall {
* @param listener to listen to the audio call events of this object
* @see #setListener(Listener, boolean)
*/
- void setListener(Listener listener);
+ public void setListener(SipAudioCall.Listener listener) {
+ setListener(listener, false);
+ }
/**
* Sets the listener to listen to the audio call events. A
@@ -150,44 +216,355 @@ public interface SipAudioCall {
* @param callbackImmediately set to true if the caller wants to be called
* back immediately on the current state
*/
- void setListener(Listener listener, boolean callbackImmediately);
+ public void setListener(SipAudioCall.Listener listener,
+ boolean callbackImmediately) {
+ mListener = listener;
+ try {
+ if ((listener == null) || !callbackImmediately) {
+ // do nothing
+ } else if (mErrorCode != SipErrorCode.NO_ERROR) {
+ listener.onError(this, mErrorCode, mErrorMessage);
+ } else if (mInCall) {
+ if (mHold) {
+ listener.onCallHeld(this);
+ } else {
+ listener.onCallEstablished(this);
+ }
+ } else {
+ int state = getState();
+ switch (state) {
+ case SipSession.State.READY_TO_CALL:
+ listener.onReadyToCall(this);
+ break;
+ case SipSession.State.INCOMING_CALL:
+ listener.onRinging(this, getPeerProfile());
+ break;
+ case SipSession.State.OUTGOING_CALL:
+ listener.onCalling(this);
+ break;
+ case SipSession.State.OUTGOING_CALL_RING_BACK:
+ listener.onRingingBack(this);
+ break;
+ }
+ }
+ } catch (Throwable t) {
+ Log.e(TAG, "setListener()", t);
+ }
+ }
+
+ /**
+ * Checks if the call is established.
+ *
+ * @return true if the call is established
+ */
+ public synchronized boolean isInCall() {
+ return mInCall;
+ }
+
+ /**
+ * Checks if the call is on hold.
+ *
+ * @return true if the call is on hold
+ */
+ public synchronized boolean isOnHold() {
+ return mHold;
+ }
/**
* Closes this object. This object is not usable after being closed.
*/
- void close();
+ public void close() {
+ close(true);
+ }
+
+ private synchronized void close(boolean closeRtp) {
+ if (closeRtp) stopCall(RELEASE_SOCKET);
+ stopRingbackTone();
+ stopRinging();
+
+ mInCall = false;
+ mHold = false;
+ mSessionId = System.currentTimeMillis();
+ mErrorCode = SipErrorCode.NO_ERROR;
+ mErrorMessage = null;
+
+ if (mSipSession != null) {
+ mSipSession.setListener(null);
+ mSipSession = null;
+ }
+ }
/**
- * Initiates an audio call to the specified profile. The attempt will be
- * timed out if the call is not established within {@code timeout} seconds
- * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
- * will be called.
+ * Gets the local SIP profile.
*
- * @param callee the SIP profile to make the call to
- * @param sipManager the {@link SipManager} object to help make call with
- * @param timeout the timeout value in seconds
- * @see Listener.onError
+ * @return the local SIP profile
*/
- void makeCall(SipProfile callee, SipManager sipManager, int timeout)
- throws SipException;
+ public synchronized SipProfile getLocalProfile() {
+ return mLocalProfile;
+ }
/**
- * Starts the audio for the established call. This method should be called
- * after {@link Listener#onCallEstablished} is called.
+ * Gets the peer's SIP profile.
+ *
+ * @return the peer's SIP profile
*/
- void startAudio();
+ public synchronized SipProfile getPeerProfile() {
+ return (mSipSession == null) ? null : mSipSession.getPeerProfile();
+ }
+
+ /**
+ * Gets the state of the {@link SipSession} that carries this call.
+ * The value returned must be one of the states in {@link SipSession.State}.
+ *
+ * @return the session state
+ */
+ public synchronized int getState() {
+ if (mSipSession == null) return SipSession.State.READY_TO_CALL;
+ return mSipSession.getState();
+ }
+
+
+ /**
+ * Gets the {@link SipSession} that carries this call.
+ *
+ * @return the session object that carries this call
+ * @hide
+ */
+ public synchronized SipSession getSipSession() {
+ return mSipSession;
+ }
+
+ private SipSession.Listener createListener() {
+ return new SipSession.Listener() {
+ @Override
+ public void onCalling(SipSession session) {
+ Log.d(TAG, "calling... " + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCalling(SipAudioCall.this);
+ } catch (Throwable t) {
+ Log.i(TAG, "onCalling(): " + t);
+ }
+ }
+ }
+
+ @Override
+ public void onRingingBack(SipSession session) {
+ Log.d(TAG, "sip call ringing back: " + session);
+ if (!mInCall) startRingbackTone();
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onRingingBack(SipAudioCall.this);
+ } catch (Throwable t) {
+ Log.i(TAG, "onRingingBack(): " + t);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void onRinging(SipSession session,
+ SipProfile peerProfile, String sessionDescription) {
+ if ((mSipSession == null) || !mInCall
+ || !session.getCallId().equals(mSipSession.getCallId())) {
+ // should not happen
+ session.endCall();
+ return;
+ }
+
+ // session changing request
+ try {
+ String answer = createAnswer(sessionDescription).encode();
+ mSipSession.answerCall(answer, SESSION_TIMEOUT);
+ } catch (Throwable e) {
+ Log.e(TAG, "onRinging()", e);
+ session.endCall();
+ }
+ }
+
+ @Override
+ public void onCallEstablished(SipSession session,
+ String sessionDescription) {
+ stopRingbackTone();
+ stopRinging();
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "onCallEstablished()" + mPeerSd);
+
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ if (mHold) {
+ listener.onCallHeld(SipAudioCall.this);
+ } else {
+ listener.onCallEstablished(SipAudioCall.this);
+ }
+ } catch (Throwable t) {
+ Log.i(TAG, "onCallEstablished(): " + t);
+ }
+ }
+ }
+
+ @Override
+ public void onCallEnded(SipSession session) {
+ Log.d(TAG, "sip call ended: " + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCallEnded(SipAudioCall.this);
+ } catch (Throwable t) {
+ Log.i(TAG, "onCallEnded(): " + t);
+ }
+ }
+ close();
+ }
+
+ @Override
+ public void onCallBusy(SipSession session) {
+ Log.d(TAG, "sip call busy: " + session);
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onCallBusy(SipAudioCall.this);
+ } catch (Throwable t) {
+ Log.i(TAG, "onCallBusy(): " + t);
+ }
+ }
+ close(false);
+ }
+
+ @Override
+ public void onCallChangeFailed(SipSession session, int errorCode,
+ String message) {
+ Log.d(TAG, "sip call change failed: " + message);
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onError(SipAudioCall.this, mErrorCode,
+ message);
+ } catch (Throwable t) {
+ Log.i(TAG, "onCallBusy(): " + t);
+ }
+ }
+ }
+
+ @Override
+ public void onError(SipSession session, int errorCode,
+ String message) {
+ SipAudioCall.this.onError(errorCode, message);
+ }
+
+ @Override
+ public void onRegistering(SipSession session) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationTimeout(SipSession session) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationFailed(SipSession session, int errorCode,
+ String message) {
+ // irrelevant
+ }
+
+ @Override
+ public void onRegistrationDone(SipSession session, int duration) {
+ // irrelevant
+ }
+ };
+ }
+
+ private void onError(int errorCode, String message) {
+ Log.d(TAG, "sip session error: "
+ + SipErrorCode.toString(errorCode) + ": " + message);
+ mErrorCode = errorCode;
+ mErrorMessage = message;
+ Listener listener = mListener;
+ if (listener != null) {
+ try {
+ listener.onError(this, errorCode, message);
+ } catch (Throwable t) {
+ Log.i(TAG, "onError(): " + t);
+ }
+ }
+ synchronized (this) {
+ if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
+ || !isInCall()) {
+ close(true);
+ }
+ }
+ }
/**
* Attaches an incoming call to this call object.
*
* @param session the session that receives the incoming call
* @param sessionDescription the session description of the incoming call
+ * @throws SipException if the SIP service fails to attach this object to
+ * the session
+ */
+ public synchronized void attachCall(SipSession session,
+ String sessionDescription) throws SipException {
+ mSipSession = session;
+ mPeerSd = sessionDescription;
+ Log.v(TAG, "attachCall()" + mPeerSd);
+ try {
+ session.setListener(createListener());
+
+ if (getState() == SipSession.State.INCOMING_CALL) startRinging();
+ } catch (Throwable e) {
+ Log.e(TAG, "attachCall()", e);
+ throwSipException(e);
+ }
+ }
+
+ /**
+ * Initiates an audio call to the specified profile. The attempt will be
+ * timed out if the call is not established within {@code timeout} seconds
+ * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
+ * will be called.
+ *
+ * @param callee the SIP profile to make the call to
+ * @param sipManager the {@link SipManager} object to help make call with
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener.onError
+ * @throws SipException if the SIP service fails to create a session for the
+ * call
*/
- void attachCall(ISipSession session, String sessionDescription)
- throws SipException;
+ public synchronized void makeCall(SipProfile peerProfile,
+ SipManager sipManager, int timeout) throws SipException {
+ SipSession s = mSipSession = sipManager.createSipSession(
+ mLocalProfile, createListener());
+ if (s == null) {
+ throw new SipException(
+ "Failed to create SipSession; network available?");
+ }
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ s.makeCall(peerProfile, createOffer().encode(), timeout);
+ } catch (IOException e) {
+ throw new SipException("makeCall()", e);
+ }
+ }
- /** Ends a call. */
- void endCall() throws SipException;
+ /**
+ * Ends a call.
+ * @throws SipException if the SIP service fails to end the call
+ */
+ public synchronized void endCall() throws SipException {
+ stopRinging();
+ stopCall(RELEASE_SOCKET);
+ mInCall = false;
+
+ // perform the above local ops first and then network op
+ if (mSipSession != null) mSipSession.endCall();
+ }
/**
* Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is
@@ -196,10 +573,19 @@ public interface SipAudioCall {
* {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
- * @param timeout the timeout value in seconds
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
* @see Listener.onError
+ * @throws SipException if the SIP service fails to hold the call
*/
- void holdCall(int timeout) throws SipException;
+ public synchronized void holdCall(int timeout) throws SipException {
+ if (mHold) return;
+ mSipSession.changeCall(createHoldOffer().encode(), timeout);
+ mHold = true;
+
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ }
/**
* Answers a call. The attempt will be timed out if the call is not
@@ -207,10 +593,20 @@ public interface SipAudioCall {
* {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
- * @param timeout the timeout value in seconds
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
* @see Listener.onError
+ * @throws SipException if the SIP service fails to answer the call
*/
- void answerCall(int timeout) throws SipException;
+ public synchronized void answerCall(int timeout) throws SipException {
+ stopRinging();
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+ mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
+ } catch (IOException e) {
+ throw new SipException("answerCall()", e);
+ }
+ }
/**
* Continues a call that's on hold. When succeeds,
@@ -219,45 +615,189 @@ public interface SipAudioCall {
* {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
- * @param timeout the timeout value in seconds
+ * @param timeout the timeout value in seconds. Default value (defined by
+ * SIP protocol) is used if {@code timeout} is zero or negative.
* @see Listener.onError
+ * @throws SipException if the SIP service fails to unhold the call
*/
- void continueCall(int timeout) throws SipException;
+ public synchronized void continueCall(int timeout) throws SipException {
+ if (!mHold) return;
+ mSipSession.changeCall(createContinueOffer().encode(), timeout);
+ mHold = false;
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
+ }
- /** Puts the device to speaker mode. */
- void setSpeakerMode(boolean speakerMode);
+ private SimpleSessionDescription createOffer() {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec[] codecs = AudioCodec.getCodecs();
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ for (AudioCodec codec : AudioCodec.getCodecs()) {
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ }
+ media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ return offer;
+ }
- /** Toggles mute. */
- void toggleMute();
+ private SimpleSessionDescription createAnswer(String offerSd) {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(offerSd);
+ SimpleSessionDescription answer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(type, media.getRtpmap(type),
+ media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+ if (codec != null) {
+ Media reply = answer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
- /**
- * Checks if the call is on hold.
- *
- * @return true if the call is on hold
- */
- boolean isOnHold();
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ reply.setRtpPayload(
+ type, rtpmap, media.getFmtp(type));
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (media.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(media.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ } else if(offer.getAttribute("recvonly") != null) {
+ answer.setAttribute("sendonly", "");
+ } else if(offer.getAttribute("sendonly") != null) {
+ answer.setAttribute("recvonly", "");
+ }
+ continue;
+ }
+ }
+ // Reject the media.
+ Media reply = answer.newMedia(
+ media.getType(), 0, 1, media.getProtocol());
+ for (String format : media.getFormats()) {
+ reply.setFormat(format, null);
+ }
+ }
+ if (codec == null) {
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+ return answer;
+ }
+
+ private SimpleSessionDescription createHoldOffer() {
+ SimpleSessionDescription offer = createContinueOffer();
+ offer.setAttribute("sendonly", "");
+ return offer;
+ }
+
+ private SimpleSessionDescription createContinueOffer() {
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mSessionId, getLocalIp());
+ Media media = offer.newMedia(
+ "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+ AudioCodec codec = mAudioStream.getCodec();
+ media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+ int dtmfType = mAudioStream.getDtmfType();
+ if (dtmfType != -1) {
+ media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
+ }
+ return offer;
+ }
+
+ private void grabWifiHighPerfLock() {
+ if (mWifiHighPerfLock == null) {
+ Log.v(TAG, "acquire wifi high perf lock");
+ mWifiHighPerfLock = ((WifiManager)
+ mContext.getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
+ mWifiHighPerfLock.acquire();
+ }
+ }
+
+ private void releaseWifiHighPerfLock() {
+ if (mWifiHighPerfLock != null) {
+ Log.v(TAG, "release wifi high perf lock");
+ mWifiHighPerfLock.release();
+ mWifiHighPerfLock = null;
+ }
+ }
+
+ private boolean isWifiOn() {
+ return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
+ }
+
+ /** Toggles mute. */
+ public synchronized void toggleMute() {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup != null) {
+ audioGroup.setMode(
+ mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
+ mMuted = !mMuted;
+ }
+ }
/**
* Checks if the call is muted.
*
* @return true if the call is muted
*/
- boolean isMuted();
+ public synchronized boolean isMuted() {
+ return mMuted;
+ }
+
+ /** Puts the device to speaker mode. */
+ public synchronized void setSpeakerMode(boolean speakerMode) {
+ ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+ .setSpeakerphoneOn(speakerMode);
+ }
/**
- * Sends a DTMF code.
+ * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+ * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+ * flash to 16. Currently, event flash is not supported.
*
- * @param code the DTMF code to send
+ * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+ * inputs.
+ * @see http://tools.ietf.org/html/rfc2833
*/
- void sendDtmf(int code);
+ public void sendDtmf(int code) {
+ sendDtmf(code, null);
+ }
/**
- * Sends a DTMF code.
+ * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+ * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+ * flash to 16. Currently, event flash is not supported.
*
- * @param code the DTMF code to send
+ * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+ * inputs.
* @param result the result message to send when done
*/
- void sendDtmf(int code, Message result);
+ public synchronized void sendDtmf(int code, Message result) {
+ AudioGroup audioGroup = getAudioGroup();
+ if ((audioGroup != null) && (mSipSession != null)
+ && (SipSession.State.IN_CALL == getState())) {
+ Log.v(TAG, "send DTMF: " + code);
+ audioGroup.sendDtmf(code);
+ }
+ if (result != null) result.sendToTarget();
+ }
/**
* Gets the {@link AudioStream} object used in this call. The object
@@ -268,8 +808,11 @@ public interface SipAudioCall {
*
* @return the {@link AudioStream} object or null if the RTP stream has not
* yet been set up
+ * @hide
*/
- AudioStream getAudioStream();
+ public synchronized AudioStream getAudioStream() {
+ return mAudioStream;
+ }
/**
* Gets the {@link AudioGroup} object which the {@link AudioStream} object
@@ -283,8 +826,12 @@ public interface SipAudioCall {
* @return the {@link AudioGroup} object or null if the RTP stream has not
* yet been set up
* @see #getAudioStream
+ * @hide
*/
- AudioGroup getAudioGroup();
+ public synchronized AudioGroup getAudioGroup() {
+ if (mAudioGroup != null) return mAudioGroup;
+ return ((mAudioStream == null) ? null : mAudioStream.getGroup());
+ }
/**
* Sets the {@link AudioGroup} object which the {@link AudioStream} object
@@ -292,56 +839,214 @@ public interface SipAudioCall {
* will be dynamically created when needed.
*
* @see #getAudioStream
+ * @hide
*/
- void setAudioGroup(AudioGroup audioGroup);
+ public synchronized void setAudioGroup(AudioGroup group) {
+ if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
+ mAudioStream.join(group);
+ }
+ mAudioGroup = group;
+ }
/**
- * Checks if the call is established.
- *
- * @return true if the call is established
+ * Starts the audio for the established call. This method should be called
+ * after {@link Listener#onCallEstablished} is called.
*/
- boolean isInCall();
+ public void startAudio() {
+ try {
+ startAudioInternal();
+ } catch (UnknownHostException e) {
+ onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
+ } catch (Throwable e) {
+ onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
+ }
+ }
- /**
- * Gets the local SIP profile.
- *
- * @return the local SIP profile
- */
- SipProfile getLocalProfile();
+ private synchronized void startAudioInternal() throws UnknownHostException {
+ if (mPeerSd == null) {
+ Log.v(TAG, "startAudioInternal() mPeerSd = null");
+ throw new IllegalStateException("mPeerSd = null");
+ }
- /**
- * Gets the peer's SIP profile.
- *
- * @return the peer's SIP profile
- */
- SipProfile getPeerProfile();
+ stopCall(DONT_RELEASE_SOCKET);
+ mInCall = true;
- /**
- * Gets the state of the {@link ISipSession} that carries this call.
- * The value returned must be one of the states in {@link SipSessionState}.
- *
- * @return the session state
- */
- int getState();
+ // Run exact the same logic in createAnswer() to setup mAudioStream.
+ SimpleSessionDescription offer =
+ new SimpleSessionDescription(mPeerSd);
+ AudioStream stream = mAudioStream;
+ AudioCodec codec = null;
+ for (Media media : offer.getMedia()) {
+ if ((codec == null) && (media.getPort() > 0)
+ && "audio".equals(media.getType())
+ && "RTP/AVP".equals(media.getProtocol())) {
+ // Find the first audio codec we supported.
+ for (int type : media.getRtpPayloadTypes()) {
+ codec = AudioCodec.getCodec(
+ type, media.getRtpmap(type), media.getFmtp(type));
+ if (codec != null) {
+ break;
+ }
+ }
+
+ if (codec != null) {
+ // Associate with the remote host.
+ String address = media.getAddress();
+ if (address == null) {
+ address = offer.getAddress();
+ }
+ stream.associate(InetAddress.getByName(address),
+ media.getPort());
+
+ stream.setDtmfType(-1);
+ stream.setCodec(codec);
+ // Check if DTMF is supported in the same media.
+ for (int type : media.getRtpPayloadTypes()) {
+ String rtpmap = media.getRtpmap(type);
+ if ((type != codec.type) && (rtpmap != null)
+ && rtpmap.startsWith("telephone-event")) {
+ stream.setDtmfType(type);
+ }
+ }
+
+ // Handle recvonly and sendonly.
+ if (mHold) {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ } else if (media.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(media.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else if(offer.getAttribute("recvonly") != null) {
+ stream.setMode(RtpStream.MODE_SEND_ONLY);
+ } else if(offer.getAttribute("sendonly") != null) {
+ stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+ } else {
+ stream.setMode(RtpStream.MODE_NORMAL);
+ }
+ break;
+ }
+ }
+ }
+ if (codec == null) {
+ throw new IllegalStateException("Reject SDP: no suitable codecs");
+ }
+
+ if (isWifiOn()) grabWifiHighPerfLock();
+
+ if (!mHold) {
+ /* The recorder volume will be very low if the device is in
+ * IN_CALL mode. Therefore, we have to set the mode to NORMAL
+ * in order to have the normal microphone level.
+ */
+ ((AudioManager) mContext.getSystemService
+ (Context.AUDIO_SERVICE))
+ .setMode(AudioManager.MODE_NORMAL);
+ }
+
+ // AudioGroup logic:
+ AudioGroup audioGroup = getAudioGroup();
+ if (mHold) {
+ if (audioGroup != null) {
+ audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ }
+ // don't create an AudioGroup here; doing so will fail if
+ // there's another AudioGroup out there that's active
+ } else {
+ if (audioGroup == null) audioGroup = new AudioGroup();
+ stream.join(audioGroup);
+ if (mMuted) {
+ audioGroup.setMode(AudioGroup.MODE_MUTED);
+ } else {
+ audioGroup.setMode(AudioGroup.MODE_NORMAL);
+ }
+ }
+ }
+
+ private void stopCall(boolean releaseSocket) {
+ Log.d(TAG, "stop audiocall");
+ releaseWifiHighPerfLock();
+ if (mAudioStream != null) {
+ mAudioStream.join(null);
+
+ if (releaseSocket) {
+ mAudioStream.release();
+ mAudioStream = null;
+ }
+ }
+ }
+
+ private String getLocalIp() {
+ return mSipSession.getLocalIp();
+ }
- /**
- * Gets the {@link ISipSession} that carries this call.
- *
- * @return the session object that carries this call
- */
- ISipSession getSipSession();
/**
* Enables/disables the ring-back tone.
*
* @param enabled true to enable; false to disable
*/
- void setRingbackToneEnabled(boolean enabled);
+ public synchronized void setRingbackToneEnabled(boolean enabled) {
+ mRingbackToneEnabled = enabled;
+ }
/**
* Enables/disables the ring tone.
*
* @param enabled true to enable; false to disable
*/
- void setRingtoneEnabled(boolean enabled);
+ public synchronized void setRingtoneEnabled(boolean enabled) {
+ mRingtoneEnabled = enabled;
+ }
+
+ private void startRingbackTone() {
+ if (!mRingbackToneEnabled) return;
+ if (mRingbackTone == null) {
+ // The volume relative to other sounds in the stream
+ int toneVolume = 80;
+ mRingbackTone = new ToneGenerator(
+ AudioManager.STREAM_VOICE_CALL, toneVolume);
+ }
+ mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
+ }
+
+ private void stopRingbackTone() {
+ if (mRingbackTone != null) {
+ mRingbackTone.stopTone();
+ mRingbackTone.release();
+ mRingbackTone = null;
+ }
+ }
+
+ private void startRinging() {
+ if (!mRingtoneEnabled) return;
+ ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
+ .vibrate(new long[] {0, 1000, 1000}, 1);
+ AudioManager am = (AudioManager)
+ mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+ String ringtoneUri =
+ Settings.System.DEFAULT_RINGTONE_URI.toString();
+ mRingtone = RingtoneManager.getRingtone(mContext,
+ Uri.parse(ringtoneUri));
+ mRingtone.play();
+ }
+ }
+
+ private void stopRinging() {
+ ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
+ .cancel();
+ if (mRingtone != null) mRingtone.stop();
+ }
+
+ private void throwSipException(Throwable throwable) throws SipException {
+ if (throwable instanceof SipException) {
+ throw (SipException) throwable;
+ } else {
+ throw new SipException("", throwable);
+ }
+ }
+
+ private SipProfile getPeerProfile(SipSession session) {
+ return session.getPeerProfile();
+ }
}
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
deleted file mode 100644
index 2f8d175..0000000
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ /dev/null
@@ -1,766 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.media.ToneGenerator;
-import android.net.Uri;
-import android.net.rtp.AudioCodec;
-import android.net.rtp.AudioGroup;
-import android.net.rtp.AudioStream;
-import android.net.rtp.RtpStream;
-import android.net.sip.SimpleSessionDescription.Media;
-import android.net.wifi.WifiManager;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class that handles an audio call over SIP.
- */
-/** @hide */
-public class SipAudioCallImpl extends SipSessionAdapter
- implements SipAudioCall {
- private static final String TAG = SipAudioCallImpl.class.getSimpleName();
- private static final boolean RELEASE_SOCKET = true;
- private static final boolean DONT_RELEASE_SOCKET = false;
- private static final int SESSION_TIMEOUT = 5; // in seconds
-
- private Context mContext;
- private SipProfile mLocalProfile;
- private SipAudioCall.Listener mListener;
- private ISipSession mSipSession;
-
- private long mSessionId = System.currentTimeMillis();
- private String mPeerSd;
-
- private AudioStream mAudioStream;
- private AudioGroup mAudioGroup;
-
- private boolean mInCall = false;
- private boolean mMuted = false;
- private boolean mHold = false;
-
- private boolean mRingbackToneEnabled = true;
- private boolean mRingtoneEnabled = true;
- private Ringtone mRingtone;
- private ToneGenerator mRingbackTone;
-
- private SipProfile mPendingCallRequest;
- private WifiManager mWm;
- private WifiManager.WifiLock mWifiHighPerfLock;
-
- private int mErrorCode = SipErrorCode.NO_ERROR;
- private String mErrorMessage;
-
- public SipAudioCallImpl(Context context, SipProfile localProfile) {
- mContext = context;
- mLocalProfile = localProfile;
- mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- }
-
- public void setListener(SipAudioCall.Listener listener) {
- setListener(listener, false);
- }
-
- public void setListener(SipAudioCall.Listener listener,
- boolean callbackImmediately) {
- mListener = listener;
- try {
- if ((listener == null) || !callbackImmediately) {
- // do nothing
- } else if (mErrorCode != SipErrorCode.NO_ERROR) {
- listener.onError(this, mErrorCode, mErrorMessage);
- } else if (mInCall) {
- if (mHold) {
- listener.onCallHeld(this);
- } else {
- listener.onCallEstablished(this);
- }
- } else {
- int state = getState();
- switch (state) {
- case SipSessionState.READY_TO_CALL:
- listener.onReadyToCall(this);
- break;
- case SipSessionState.INCOMING_CALL:
- listener.onRinging(this, getPeerProfile(mSipSession));
- break;
- case SipSessionState.OUTGOING_CALL:
- listener.onCalling(this);
- break;
- case SipSessionState.OUTGOING_CALL_RING_BACK:
- listener.onRingingBack(this);
- break;
- }
- }
- } catch (Throwable t) {
- Log.e(TAG, "setListener()", t);
- }
- }
-
- public synchronized boolean isInCall() {
- return mInCall;
- }
-
- public synchronized boolean isOnHold() {
- return mHold;
- }
-
- public void close() {
- close(true);
- }
-
- private synchronized void close(boolean closeRtp) {
- if (closeRtp) stopCall(RELEASE_SOCKET);
- stopRingbackTone();
- stopRinging();
-
- mInCall = false;
- mHold = false;
- mSessionId = System.currentTimeMillis();
- mErrorCode = SipErrorCode.NO_ERROR;
- mErrorMessage = null;
-
- if (mSipSession != null) {
- try {
- mSipSession.setListener(null);
- } catch (RemoteException e) {
- // don't care
- }
- mSipSession = null;
- }
- }
-
- public synchronized SipProfile getLocalProfile() {
- return mLocalProfile;
- }
-
- public synchronized SipProfile getPeerProfile() {
- try {
- return (mSipSession == null) ? null : mSipSession.getPeerProfile();
- } catch (RemoteException e) {
- return null;
- }
- }
-
- public synchronized int getState() {
- if (mSipSession == null) return SipSessionState.READY_TO_CALL;
- try {
- return mSipSession.getState();
- } catch (RemoteException e) {
- return SipSessionState.REMOTE_ERROR;
- }
- }
-
-
- public synchronized ISipSession getSipSession() {
- return mSipSession;
- }
-
- @Override
- public void onCalling(ISipSession session) {
- Log.d(TAG, "calling... " + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCalling(this);
- } catch (Throwable t) {
- Log.e(TAG, "onCalling()", t);
- }
- }
- }
-
- @Override
- public void onRingingBack(ISipSession session) {
- Log.d(TAG, "sip call ringing back: " + session);
- if (!mInCall) startRingbackTone();
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onRingingBack(this);
- } catch (Throwable t) {
- Log.e(TAG, "onRingingBack()", t);
- }
- }
- }
-
- @Override
- public synchronized void onRinging(ISipSession session,
- SipProfile peerProfile, String sessionDescription) {
- try {
- if ((mSipSession == null) || !mInCall
- || !session.getCallId().equals(mSipSession.getCallId())) {
- // should not happen
- session.endCall();
- return;
- }
-
- // session changing request
- try {
- String answer = createAnswer(sessionDescription).encode();
- mSipSession.answerCall(answer, SESSION_TIMEOUT);
- } catch (Throwable e) {
- Log.e(TAG, "onRinging()", e);
- session.endCall();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "onRinging()", e);
- }
- }
-
- @Override
- public void onCallEstablished(ISipSession session,
- String sessionDescription) {
- stopRingbackTone();
- stopRinging();
- mPeerSd = sessionDescription;
- Log.v(TAG, "onCallEstablished()" + mPeerSd);
-
- Listener listener = mListener;
- if (listener != null) {
- try {
- if (mHold) {
- listener.onCallHeld(this);
- } else {
- listener.onCallEstablished(this);
- }
- } catch (Throwable t) {
- Log.e(TAG, "onCallEstablished()", t);
- }
- }
- }
-
- @Override
- public void onCallEnded(ISipSession session) {
- Log.d(TAG, "sip call ended: " + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCallEnded(this);
- } catch (Throwable t) {
- Log.e(TAG, "onCallEnded()", t);
- }
- }
- close();
- }
-
- @Override
- public void onCallBusy(ISipSession session) {
- Log.d(TAG, "sip call busy: " + session);
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onCallBusy(this);
- } catch (Throwable t) {
- Log.e(TAG, "onCallBusy()", t);
- }
- }
- close(false);
- }
-
- @Override
- public void onCallChangeFailed(ISipSession session, int errorCode,
- String message) {
- Log.d(TAG, "sip call change failed: " + message);
- mErrorCode = errorCode;
- mErrorMessage = message;
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onError(this, mErrorCode, message);
- } catch (Throwable t) {
- Log.e(TAG, "onCallBusy()", t);
- }
- }
- }
-
- @Override
- public void onError(ISipSession session, int errorCode, String message) {
- Log.d(TAG, "sip session error: " + SipErrorCode.toString(errorCode)
- + ": " + message);
- mErrorCode = errorCode;
- mErrorMessage = message;
- Listener listener = mListener;
- if (listener != null) {
- try {
- listener.onError(this, errorCode, message);
- } catch (Throwable t) {
- Log.e(TAG, "onError()", t);
- }
- }
- synchronized (this) {
- if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
- || !isInCall()) {
- close(true);
- }
- }
- }
-
- public synchronized void attachCall(ISipSession session,
- String sessionDescription) throws SipException {
- mSipSession = session;
- mPeerSd = sessionDescription;
- Log.v(TAG, "attachCall()" + mPeerSd);
- try {
- session.setListener(this);
- if (getState() == SipSessionState.INCOMING_CALL) startRinging();
- } catch (Throwable e) {
- Log.e(TAG, "attachCall()", e);
- throwSipException(e);
- }
- }
-
- public synchronized void makeCall(SipProfile peerProfile,
- SipManager sipManager, int timeout) throws SipException {
- try {
- mSipSession = sipManager.createSipSession(mLocalProfile, this);
- if (mSipSession == null) {
- throw new SipException(
- "Failed to create SipSession; network available?");
- }
- mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
- mSipSession.makeCall(peerProfile, createOffer().encode(), timeout);
- } catch (Throwable e) {
- if (e instanceof SipException) {
- throw (SipException) e;
- } else {
- throwSipException(e);
- }
- }
- }
-
- public synchronized void endCall() throws SipException {
- try {
- stopRinging();
- stopCall(RELEASE_SOCKET);
- mInCall = false;
-
- // perform the above local ops first and then network op
- if (mSipSession != null) mSipSession.endCall();
- } catch (Throwable e) {
- throwSipException(e);
- }
- }
-
- public synchronized void answerCall(int timeout) throws SipException {
- try {
- stopRinging();
- mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
- mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
- } catch (Throwable e) {
- Log.e(TAG, "answerCall()", e);
- throwSipException(e);
- }
- }
-
- public synchronized void holdCall(int timeout) throws SipException {
- if (mHold) return;
- try {
- mSipSession.changeCall(createHoldOffer().encode(), timeout);
- } catch (Throwable e) {
- throwSipException(e);
- }
- mHold = true;
- AudioGroup audioGroup = getAudioGroup();
- if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
- }
-
- public synchronized void continueCall(int timeout) throws SipException {
- if (!mHold) return;
- try {
- mSipSession.changeCall(createContinueOffer().encode(), timeout);
- } catch (Throwable e) {
- throwSipException(e);
- }
- mHold = false;
- AudioGroup audioGroup = getAudioGroup();
- if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
- }
-
- private SimpleSessionDescription createOffer() {
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- AudioCodec[] codecs = AudioCodec.getCodecs();
- Media media = offer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- for (AudioCodec codec : AudioCodec.getCodecs()) {
- media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
- }
- media.setRtpPayload(127, "telephone-event/8000", "0-15");
- return offer;
- }
-
- private SimpleSessionDescription createAnswer(String offerSd) {
- SimpleSessionDescription offer =
- new SimpleSessionDescription(offerSd);
- SimpleSessionDescription answer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- AudioCodec codec = null;
- for (Media media : offer.getMedia()) {
- if ((codec == null) && (media.getPort() > 0)
- && "audio".equals(media.getType())
- && "RTP/AVP".equals(media.getProtocol())) {
- // Find the first audio codec we supported.
- for (int type : media.getRtpPayloadTypes()) {
- codec = AudioCodec.getCodec(type, media.getRtpmap(type),
- media.getFmtp(type));
- if (codec != null) {
- break;
- }
- }
- if (codec != null) {
- Media reply = answer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
-
- // Check if DTMF is supported in the same media.
- for (int type : media.getRtpPayloadTypes()) {
- String rtpmap = media.getRtpmap(type);
- if ((type != codec.type) && (rtpmap != null)
- && rtpmap.startsWith("telephone-event")) {
- reply.setRtpPayload(
- type, rtpmap, media.getFmtp(type));
- }
- }
-
- // Handle recvonly and sendonly.
- if (media.getAttribute("recvonly") != null) {
- answer.setAttribute("sendonly", "");
- } else if(media.getAttribute("sendonly") != null) {
- answer.setAttribute("recvonly", "");
- } else if(offer.getAttribute("recvonly") != null) {
- answer.setAttribute("sendonly", "");
- } else if(offer.getAttribute("sendonly") != null) {
- answer.setAttribute("recvonly", "");
- }
- continue;
- }
- }
- // Reject the media.
- Media reply = answer.newMedia(
- media.getType(), 0, 1, media.getProtocol());
- for (String format : media.getFormats()) {
- reply.setFormat(format, null);
- }
- }
- if (codec == null) {
- throw new IllegalStateException("Reject SDP: no suitable codecs");
- }
- return answer;
- }
-
- private SimpleSessionDescription createHoldOffer() {
- SimpleSessionDescription offer = createContinueOffer();
- offer.setAttribute("sendonly", "");
- return offer;
- }
-
- private SimpleSessionDescription createContinueOffer() {
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mSessionId, getLocalIp());
- Media media = offer.newMedia(
- "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
- AudioCodec codec = mAudioStream.getCodec();
- media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
- int dtmfType = mAudioStream.getDtmfType();
- if (dtmfType != -1) {
- media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
- }
- return offer;
- }
-
- private void grabWifiHighPerfLock() {
- if (mWifiHighPerfLock == null) {
- Log.v(TAG, "acquire wifi high perf lock");
- mWifiHighPerfLock = ((WifiManager)
- mContext.getSystemService(Context.WIFI_SERVICE))
- .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
- mWifiHighPerfLock.acquire();
- }
- }
-
- private void releaseWifiHighPerfLock() {
- if (mWifiHighPerfLock != null) {
- Log.v(TAG, "release wifi high perf lock");
- mWifiHighPerfLock.release();
- mWifiHighPerfLock = null;
- }
- }
-
- private boolean isWifiOn() {
- return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
- }
-
- public synchronized void toggleMute() {
- AudioGroup audioGroup = getAudioGroup();
- if (audioGroup != null) {
- audioGroup.setMode(
- mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
- mMuted = !mMuted;
- }
- }
-
- public synchronized boolean isMuted() {
- return mMuted;
- }
-
- public synchronized void setSpeakerMode(boolean speakerMode) {
- ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
- .setSpeakerphoneOn(speakerMode);
- }
-
- public void sendDtmf(int code) {
- sendDtmf(code, null);
- }
-
- public synchronized void sendDtmf(int code, Message result) {
- AudioGroup audioGroup = getAudioGroup();
- if ((audioGroup != null) && (mSipSession != null)
- && (SipSessionState.IN_CALL == getState())) {
- Log.v(TAG, "send DTMF: " + code);
- audioGroup.sendDtmf(code);
- }
- if (result != null) result.sendToTarget();
- }
-
- public synchronized AudioStream getAudioStream() {
- return mAudioStream;
- }
-
- public synchronized AudioGroup getAudioGroup() {
- if (mAudioGroup != null) return mAudioGroup;
- return ((mAudioStream == null) ? null : mAudioStream.getGroup());
- }
-
- public synchronized void setAudioGroup(AudioGroup group) {
- if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
- mAudioStream.join(group);
- }
- mAudioGroup = group;
- }
-
- public void startAudio() {
- try {
- startAudioInternal();
- } catch (UnknownHostException e) {
- onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE,
- e.getMessage());
- } catch (Throwable e) {
- onError(mSipSession, SipErrorCode.CLIENT_ERROR,
- e.getMessage());
- }
- }
-
- private synchronized void startAudioInternal() throws UnknownHostException {
- if (mPeerSd == null) {
- Log.v(TAG, "startAudioInternal() mPeerSd = null");
- throw new IllegalStateException("mPeerSd = null");
- }
-
- stopCall(DONT_RELEASE_SOCKET);
- mInCall = true;
-
- // Run exact the same logic in createAnswer() to setup mAudioStream.
- SimpleSessionDescription offer =
- new SimpleSessionDescription(mPeerSd);
- AudioStream stream = mAudioStream;
- AudioCodec codec = null;
- for (Media media : offer.getMedia()) {
- if ((codec == null) && (media.getPort() > 0)
- && "audio".equals(media.getType())
- && "RTP/AVP".equals(media.getProtocol())) {
- // Find the first audio codec we supported.
- for (int type : media.getRtpPayloadTypes()) {
- codec = AudioCodec.getCodec(
- type, media.getRtpmap(type), media.getFmtp(type));
- if (codec != null) {
- break;
- }
- }
-
- if (codec != null) {
- // Associate with the remote host.
- String address = media.getAddress();
- if (address == null) {
- address = offer.getAddress();
- }
- stream.associate(InetAddress.getByName(address),
- media.getPort());
-
- stream.setDtmfType(-1);
- stream.setCodec(codec);
- // Check if DTMF is supported in the same media.
- for (int type : media.getRtpPayloadTypes()) {
- String rtpmap = media.getRtpmap(type);
- if ((type != codec.type) && (rtpmap != null)
- && rtpmap.startsWith("telephone-event")) {
- stream.setDtmfType(type);
- }
- }
-
- // Handle recvonly and sendonly.
- if (mHold) {
- stream.setMode(RtpStream.MODE_NORMAL);
- } else if (media.getAttribute("recvonly") != null) {
- stream.setMode(RtpStream.MODE_SEND_ONLY);
- } else if(media.getAttribute("sendonly") != null) {
- stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- } else if(offer.getAttribute("recvonly") != null) {
- stream.setMode(RtpStream.MODE_SEND_ONLY);
- } else if(offer.getAttribute("sendonly") != null) {
- stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
- } else {
- stream.setMode(RtpStream.MODE_NORMAL);
- }
- break;
- }
- }
- }
- if (codec == null) {
- throw new IllegalStateException("Reject SDP: no suitable codecs");
- }
-
- if (isWifiOn()) grabWifiHighPerfLock();
-
- if (!mHold) {
- /* The recorder volume will be very low if the device is in
- * IN_CALL mode. Therefore, we have to set the mode to NORMAL
- * in order to have the normal microphone level.
- */
- ((AudioManager) mContext.getSystemService
- (Context.AUDIO_SERVICE))
- .setMode(AudioManager.MODE_NORMAL);
- }
-
- // AudioGroup logic:
- AudioGroup audioGroup = getAudioGroup();
- if (mHold) {
- if (audioGroup != null) {
- audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
- }
- // don't create an AudioGroup here; doing so will fail if
- // there's another AudioGroup out there that's active
- } else {
- if (audioGroup == null) audioGroup = new AudioGroup();
- mAudioStream.join(audioGroup);
- if (mMuted) {
- audioGroup.setMode(AudioGroup.MODE_MUTED);
- } else {
- audioGroup.setMode(AudioGroup.MODE_NORMAL);
- }
- }
- }
-
- private void stopCall(boolean releaseSocket) {
- Log.d(TAG, "stop audiocall");
- releaseWifiHighPerfLock();
- if (mAudioStream != null) {
- mAudioStream.join(null);
-
- if (releaseSocket) {
- mAudioStream.release();
- mAudioStream = null;
- }
- }
- }
-
- private String getLocalIp() {
- try {
- return mSipSession.getLocalIp();
- } catch (RemoteException e) {
- throw new IllegalStateException(e);
- }
- }
-
- public synchronized void setRingbackToneEnabled(boolean enabled) {
- mRingbackToneEnabled = enabled;
- }
-
- public synchronized void setRingtoneEnabled(boolean enabled) {
- mRingtoneEnabled = enabled;
- }
-
- private void startRingbackTone() {
- if (!mRingbackToneEnabled) return;
- if (mRingbackTone == null) {
- // The volume relative to other sounds in the stream
- int toneVolume = 80;
- mRingbackTone = new ToneGenerator(
- AudioManager.STREAM_VOICE_CALL, toneVolume);
- }
- mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
- }
-
- private void stopRingbackTone() {
- if (mRingbackTone != null) {
- mRingbackTone.stopTone();
- mRingbackTone.release();
- mRingbackTone = null;
- }
- }
-
- private void startRinging() {
- if (!mRingtoneEnabled) return;
- ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
- .vibrate(new long[] {0, 1000, 1000}, 1);
- AudioManager am = (AudioManager)
- mContext.getSystemService(Context.AUDIO_SERVICE);
- if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
- String ringtoneUri =
- Settings.System.DEFAULT_RINGTONE_URI.toString();
- mRingtone = RingtoneManager.getRingtone(mContext,
- Uri.parse(ringtoneUri));
- mRingtone.play();
- }
- }
-
- private void stopRinging() {
- ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
- .cancel();
- if (mRingtone != null) mRingtone.stop();
- }
-
- private void throwSipException(Throwable throwable) throws SipException {
- if (throwable instanceof SipException) {
- throw (SipException) throwable;
- } else {
- throw new SipException("", throwable);
- }
- }
-
- private SipProfile getPeerProfile(ISipSession session) {
- try {
- return session.getPeerProfile();
- } catch (RemoteException e) {
- return null;
- }
- }
-}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 31768d7..5976a04 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -30,8 +30,9 @@ import java.text.ParseException;
* The class provides API for various SIP related tasks. Specifically, the API
* allows an application to:
* <ul>
- * <li>register a {@link SipProfile} to have the background SIP service listen
- * to incoming calls and broadcast them with registered command string. See
+ * <li>open a {@link SipProfile} to get ready for making outbound calls or have
+ * the background SIP service listen to incoming calls and broadcast them
+ * with registered command string. See
* {@link #open(SipProfile, String, SipRegistrationListener)},
* {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
* {@link #isRegistered}. It also facilitates handling of the incoming call
@@ -40,39 +41,59 @@ import java.text.ParseException;
* {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
* <li>make/take SIP-based audio calls. See
* {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
- * <li>register/unregister with a SIP service provider. See
+ * <li>register/unregister with a SIP service provider manually. See
* {@link #register} and {@link #unregister}.</li>
- * <li>process SIP events directly with a {@link ISipSession} created by
+ * <li>process SIP events directly with a {@link SipSession} created by
* {@link #createSipSession}.</li>
* </ul>
* @hide
*/
public class SipManager {
- /** @hide */
- public static final String SIP_INCOMING_CALL_ACTION =
+ /**
+ * Action string for the incoming call intent for the Phone app.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_INCOMING_CALL =
"com.android.phone.SIP_INCOMING_CALL";
- /** @hide */
- public static final String SIP_ADD_PHONE_ACTION =
+ /**
+ * Action string for the add-phone intent.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_ADD_PHONE =
"com.android.phone.SIP_ADD_PHONE";
- /** @hide */
- public static final String SIP_REMOVE_PHONE_ACTION =
+ /**
+ * Action string for the remove-phone intent.
+ * Internal use only.
+ * @hide
+ */
+ public static final String ACTION_SIP_REMOVE_PHONE =
"com.android.phone.SIP_REMOVE_PHONE";
- /** @hide */
- public static final String LOCAL_URI_KEY = "LOCAL SIPURI";
+ /**
+ * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
+ * Internal use only.
+ * @hide
+ */
+ public static final String EXTRA_LOCAL_URI = "android:localSipUri";
- private static final String CALL_ID_KEY = "CallID";
- private static final String OFFER_SD_KEY = "OfferSD";
+ /** Part of the incoming call intent. */
+ public static final String EXTRA_CALL_ID = "android:sipCallID";
+
+ /** Part of the incoming call intent. */
+ public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
private ISipService mSipService;
+ private Context mContext;
/**
- * Gets a manager instance. Returns null if SIP API is not supported.
+ * Creates a manager instance. Returns null if SIP API is not supported.
*
- * @param context application context for checking if SIP API is supported
+ * @param context application context for creating the manager object
* @return the manager instance or null if SIP API is not supported
*/
- public static SipManager getInstance(Context context) {
- return (isApiSupported(context) ? new SipManager() : null);
+ public static SipManager newInstance(Context context) {
+ return (isApiSupported(context) ? new SipManager(context) : null);
}
/**
@@ -80,7 +101,7 @@ public class SipManager {
*/
public static boolean isApiSupported(Context context) {
return true;
- /*
+ /* TODO: uncomment this before ship
return context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SIP);
*/
@@ -91,7 +112,7 @@ public class SipManager {
*/
public static boolean isVoipSupported(Context context) {
return true;
- /*
+ /* TODO: uncomment this before ship
return context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
*/
@@ -105,23 +126,21 @@ public class SipManager {
com.android.internal.R.bool.config_sip_wifi_only);
}
- private SipManager() {
+ private SipManager(Context context) {
+ mContext = context;
createSipService();
}
private void createSipService() {
- if (mSipService != null) return;
IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
mSipService = ISipService.Stub.asInterface(b);
}
/**
- * Opens the profile for making calls and/or receiving calls. Subsequent
- * SIP calls can be made through the default phone UI. The caller may also
- * make subsequent calls through {@link #makeAudioCall}.
- * If the receiving-call option is enabled in the profile, the SIP service
- * will register the profile to the corresponding server periodically in
- * order to receive calls from the server.
+ * Opens the profile for making calls. The caller may make subsequent calls
+ * through {@link #makeAudioCall}. If one also wants to receive calls on the
+ * profile, use {@link #open(SipProfile, String, SipRegistrationListener)}
+ * instead.
*
* @param localProfile the SIP profile to make calls from
* @throws SipException if the profile contains incorrect settings or
@@ -136,12 +155,11 @@ public class SipManager {
}
/**
- * Opens the profile for making calls and/or receiving calls. Subsequent
- * SIP calls can be made through the default phone UI. The caller may also
- * make subsequent calls through {@link #makeAudioCall}.
- * If the receiving-call option is enabled in the profile, the SIP service
- * will register the profile to the corresponding server periodically in
- * order to receive calls from the server.
+ * Opens the profile for making calls and/or receiving calls. The caller may
+ * make subsequent calls through {@link #makeAudioCall}. If the
+ * auto-registration option is enabled in the profile, the SIP service
+ * will register the profile to the corresponding SIP provider periodically
+ * in order to receive calls from the provider.
*
* @param localProfile the SIP profile to receive incoming calls for
* @param incomingCallBroadcastAction the action to be broadcast when an
@@ -195,7 +213,8 @@ public class SipManager {
}
/**
- * Checks if the specified profile is enabled to receive calls.
+ * Checks if the specified profile is opened in the SIP service for
+ * making and/or receiving calls.
*
* @param localProfileUri the URI of the profile in question
* @return true if the profile is enabled to receive calls
@@ -210,11 +229,16 @@ public class SipManager {
}
/**
- * Checks if the specified profile is registered to the server for
- * receiving calls.
+ * Checks if the SIP service has successfully registered the profile to the
+ * SIP provider (specified in the profile) for receiving calls. Returning
+ * true from this method also implies the profile is opened
+ * ({@link #isOpened}).
*
* @param localProfileUri the URI of the profile in question
- * @return true if the profile is registered to the server
+ * @return true if the profile is registered to the SIP provider; false if
+ * the profile has not been opened in the SIP service or the SIP
+ * service has not yet successfully registered the profile to the SIP
+ * provider
* @throws SipException if calling the SIP service results in an error
*/
public boolean isRegistered(String localProfileUri) throws SipException {
@@ -231,7 +255,6 @@ public class SipManager {
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
- * @param context context to create a {@link SipAudioCall} object
* @param localProfile the SIP profile to make the call from
* @param peerProfile the SIP profile to make the call to
* @param listener to listen to the call events from {@link SipAudioCall};
@@ -241,10 +264,10 @@ public class SipManager {
* @throws SipException if calling the SIP service results in an error
* @see SipAudioCall.Listener.onError
*/
- public SipAudioCall makeAudioCall(Context context, SipProfile localProfile,
+ public SipAudioCall makeAudioCall(SipProfile localProfile,
SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
throws SipException {
- SipAudioCall call = new SipAudioCallImpl(context, localProfile);
+ SipAudioCall call = new SipAudioCall(mContext, localProfile);
call.setListener(listener);
call.makeCall(peerProfile, this, timeout);
return call;
@@ -257,7 +280,6 @@ public class SipManager {
* {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
* will be called.
*
- * @param context context to create a {@link SipAudioCall} object
* @param localProfileUri URI of the SIP profile to make the call from
* @param peerProfileUri URI of the SIP profile to make the call to
* @param listener to listen to the call events from {@link SipAudioCall};
@@ -267,11 +289,11 @@ public class SipManager {
* @throws SipException if calling the SIP service results in an error
* @see SipAudioCall.Listener.onError
*/
- public SipAudioCall makeAudioCall(Context context, String localProfileUri,
+ public SipAudioCall makeAudioCall(String localProfileUri,
String peerProfileUri, SipAudioCall.Listener listener, int timeout)
throws SipException {
try {
- return makeAudioCall(context,
+ return makeAudioCall(
new SipProfile.Builder(localProfileUri).build(),
new SipProfile.Builder(peerProfileUri).build(), listener,
timeout);
@@ -281,15 +303,14 @@ public class SipManager {
}
/**
- * The method calls {@code takeAudioCall(context, incomingCallIntent,
+ * The method calls {@code takeAudioCall(incomingCallIntent,
* listener, true}.
*
- * @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean)
+ * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean)
*/
- public SipAudioCall takeAudioCall(Context context,
- Intent incomingCallIntent, SipAudioCall.Listener listener)
- throws SipException {
- return takeAudioCall(context, incomingCallIntent, listener, true);
+ public SipAudioCall takeAudioCall(Intent incomingCallIntent,
+ SipAudioCall.Listener listener) throws SipException {
+ return takeAudioCall(incomingCallIntent, listener, true);
}
/**
@@ -298,16 +319,15 @@ public class SipManager {
* {@link SipAudioCall.Listener#onRinging}
* callback.
*
- * @param context context to create a {@link SipAudioCall} object
* @param incomingCallIntent the incoming call broadcast intent
* @param listener to listen to the call events from {@link SipAudioCall};
* can be null
* @return a {@link SipAudioCall} object
* @throws SipException if calling the SIP service results in an error
*/
- public SipAudioCall takeAudioCall(Context context,
- Intent incomingCallIntent, SipAudioCall.Listener listener,
- boolean ringtoneEnabled) throws SipException {
+ public SipAudioCall takeAudioCall(Intent incomingCallIntent,
+ SipAudioCall.Listener listener, boolean ringtoneEnabled)
+ throws SipException {
if (incomingCallIntent == null) return null;
String callId = getCallId(incomingCallIntent);
@@ -324,10 +344,10 @@ public class SipManager {
try {
ISipSession session = mSipService.getPendingSession(callId);
if (session == null) return null;
- SipAudioCall call = new SipAudioCallImpl(
- context, session.getLocalProfile());
+ SipAudioCall call = new SipAudioCall(
+ mContext, session.getLocalProfile());
call.setRingtoneEnabled(ringtoneEnabled);
- call.attachCall(session, offerSd);
+ call.attachCall(new SipSession(session), offerSd);
call.setListener(listener);
return call;
} catch (Throwable t) {
@@ -355,7 +375,7 @@ public class SipManager {
* @return the call ID or null if the intent does not contain it
*/
public static String getCallId(Intent incomingCallIntent) {
- return incomingCallIntent.getStringExtra(CALL_ID_KEY);
+ return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
}
/**
@@ -367,30 +387,30 @@ public class SipManager {
* have it
*/
public static String getOfferSessionDescription(Intent incomingCallIntent) {
- return incomingCallIntent.getStringExtra(OFFER_SD_KEY);
+ return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
}
/**
* Creates an incoming call broadcast intent.
*
- * @param action the action string to broadcast
* @param callId the call ID of the incoming call
* @param sessionDescription the session description of the incoming call
* @return the incoming call intent
* @hide
*/
- public static Intent createIncomingCallBroadcast(String action,
- String callId, String sessionDescription) {
- Intent intent = new Intent(action);
- intent.putExtra(CALL_ID_KEY, callId);
- intent.putExtra(OFFER_SD_KEY, sessionDescription);
+ public static Intent createIncomingCallBroadcast(String callId,
+ String sessionDescription) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_CALL_ID, callId);
+ intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
return intent;
}
/**
- * Registers the profile to the corresponding server for receiving calls.
- * {@link #open} is still needed to be called at least once in order for
- * the SIP service to broadcast an intent when an incoming call is received.
+ * Manually registers the profile to the corresponding SIP provider for
+ * receiving calls. {@link #open(SipProfile, String, SipRegistrationListener)}
+ * is still needed to be called at least once in order for the SIP service
+ * to broadcast an intent when an incoming call is received.
*
* @param localProfile the SIP profile to register with
* @param expiryTime registration expiration time (in seconds)
@@ -409,8 +429,10 @@ public class SipManager {
}
/**
- * Unregisters the profile from the corresponding server for not receiving
- * further calls.
+ * Manually unregisters the profile from the corresponding SIP provider for
+ * stop receiving further calls. This may interference with the auto
+ * registration process in the SIP service if the auto-registration option
+ * in the profile is enabled.
*
* @param localProfile the SIP profile to register with
* @param listener to listen to the registration events
@@ -460,10 +482,11 @@ public class SipManager {
* @param localProfile the SIP profile the session is associated with
* @param listener to listen to SIP session events
*/
- public ISipSession createSipSession(SipProfile localProfile,
- ISipSessionListener listener) throws SipException {
+ public SipSession createSipSession(SipProfile localProfile,
+ SipSession.Listener listener) throws SipException {
try {
- return mSipService.createSession(localProfile, listener);
+ ISipSession s = mSipService.createSession(localProfile, null);
+ return new SipSession(s, listener);
} catch (RemoteException e) {
throw new SipException("createSipSession()", e);
}
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index 88bfba9..6d5cb3c 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -48,7 +48,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
private boolean mAutoRegistration = true;
private transient int mCallingUid = 0;
- /** @hide */
public static final Parcelable.Creator<SipProfile> CREATOR =
new Parcelable.Creator<SipProfile>() {
public SipProfile createFromParcel(Parcel in) {
@@ -287,7 +286,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
mCallingUid = in.readInt();
}
- /** @hide */
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeSerializable(mAddress);
out.writeString(mProxyAddress);
@@ -300,7 +299,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable {
out.writeInt(mCallingUid);
}
- /** @hide */
+ @Override
public int describeContents() {
return 0;
}
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
new file mode 100644
index 0000000..0cc7206
--- /dev/null
+++ b/voip/java/android/net/sip/SipSession.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.sip;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A SIP session that is associated with a SIP dialog or a standalone
+ * transaction not within a dialog.
+ * @hide
+ */
+public final class SipSession {
+ private static final String TAG = "SipSession";
+
+ /**
+ * Defines {@link SipSession} states.
+ * @hide
+ */
+ public static class State {
+ /** When session is ready to initiate a call or transaction. */
+ public static final int READY_TO_CALL = 0;
+
+ /** When the registration request is sent out. */
+ public static final int REGISTERING = 1;
+
+ /** When the unregistration request is sent out. */
+ public static final int DEREGISTERING = 2;
+
+ /** When an INVITE request is received. */
+ public static final int INCOMING_CALL = 3;
+
+ /** When an OK response is sent for the INVITE request received. */
+ public static final int INCOMING_CALL_ANSWERING = 4;
+
+ /** When an INVITE request is sent. */
+ public static final int OUTGOING_CALL = 5;
+
+ /** When a RINGING response is received for the INVITE request sent. */
+ public static final int OUTGOING_CALL_RING_BACK = 6;
+
+ /** When a CANCEL request is sent for the INVITE request sent. */
+ public static final int OUTGOING_CALL_CANCELING = 7;
+
+ /** When a call is established. */
+ public static final int IN_CALL = 8;
+
+ /** When an OPTIONS request is sent. */
+ public static final int PINGING = 9;
+
+ /** Not defined. */
+ public static final int NOT_DEFINED = 101;
+
+ /**
+ * Converts the state to string.
+ */
+ public static String toString(int state) {
+ switch (state) {
+ case READY_TO_CALL:
+ return "READY_TO_CALL";
+ case REGISTERING:
+ return "REGISTERING";
+ case DEREGISTERING:
+ return "DEREGISTERING";
+ case INCOMING_CALL:
+ return "INCOMING_CALL";
+ case INCOMING_CALL_ANSWERING:
+ return "INCOMING_CALL_ANSWERING";
+ case OUTGOING_CALL:
+ return "OUTGOING_CALL";
+ case OUTGOING_CALL_RING_BACK:
+ return "OUTGOING_CALL_RING_BACK";
+ case OUTGOING_CALL_CANCELING:
+ return "OUTGOING_CALL_CANCELING";
+ case IN_CALL:
+ return "IN_CALL";
+ case PINGING:
+ return "PINGING";
+ default:
+ return "NOT_DEFINED";
+ }
+ }
+
+ private State() {
+ }
+ }
+
+ /**
+ * Listener class that listens to {@link SipSession} events.
+ * @hide
+ */
+ public static class Listener {
+ /**
+ * Called when an INVITE request is sent to initiate a new call.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onCalling(SipSession session) {
+ }
+
+ /**
+ * Called when an INVITE request is received.
+ *
+ * @param session the session object that carries out the transaction
+ * @param caller the SIP profile of the caller
+ * @param sessionDescription the caller's session description
+ */
+ public void onRinging(SipSession session, SipProfile caller,
+ String sessionDescription) {
+ }
+
+ /**
+ * Called when a RINGING response is received for the INVITE request sent
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRingingBack(SipSession session) {
+ }
+
+ /**
+ * Called when the session is established.
+ *
+ * @param session the session object that is associated with the dialog
+ * @param sessionDescription the peer's session description
+ */
+ public void onCallEstablished(SipSession session,
+ String sessionDescription) {
+ }
+
+ /**
+ * Called when the session is terminated.
+ *
+ * @param session the session object that is associated with the dialog
+ */
+ public void onCallEnded(SipSession session) {
+ }
+
+ /**
+ * Called when the peer is busy during session initialization.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onCallBusy(SipSession session) {
+ }
+
+ /**
+ * Called when an error occurs during session initialization and
+ * termination.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onError(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when an error occurs during session modification negotiation.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onCallChangeFailed(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when a registration request is sent.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRegistering(SipSession session) {
+ }
+
+ /**
+ * Called when registration is successfully done.
+ *
+ * @param session the session object that carries out the transaction
+ * @param duration duration in second before the registration expires
+ */
+ public void onRegistrationDone(SipSession session, int duration) {
+ }
+
+ /**
+ * Called when the registration fails.
+ *
+ * @param session the session object that carries out the transaction
+ * @param errorCode error code defined in {@link SipErrorCode}
+ * @param errorMessage error message
+ */
+ public void onRegistrationFailed(SipSession session, int errorCode,
+ String errorMessage) {
+ }
+
+ /**
+ * Called when the registration gets timed out.
+ *
+ * @param session the session object that carries out the transaction
+ */
+ public void onRegistrationTimeout(SipSession session) {
+ }
+ }
+
+ private final ISipSession mSession;
+ private Listener mListener;
+
+ SipSession(ISipSession realSession) {
+ mSession = realSession;
+ if (realSession != null) {
+ try {
+ realSession.setListener(createListener());
+ } catch (RemoteException e) {
+ Log.e(TAG, "SipSession.setListener(): " + e);
+ }
+ }
+ }
+
+ SipSession(ISipSession realSession, Listener listener) {
+ this(realSession);
+ setListener(listener);
+ }
+
+ /**
+ * Gets the IP address of the local host on which this SIP session runs.
+ *
+ * @return the IP address of the local host
+ */
+ public String getLocalIp() {
+ try {
+ return mSession.getLocalIp();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getLocalIp(): " + e);
+ return "127.0.0.1";
+ }
+ }
+
+ /**
+ * Gets the SIP profile that this session is associated with.
+ *
+ * @return the SIP profile that this session is associated with
+ */
+ public SipProfile getLocalProfile() {
+ try {
+ return mSession.getLocalProfile();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getLocalProfile(): " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the SIP profile that this session is connected to. Only available
+ * when the session is associated with a SIP dialog.
+ *
+ * @return the SIP profile that this session is connected to
+ */
+ public SipProfile getPeerProfile() {
+ try {
+ return mSession.getPeerProfile();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getPeerProfile(): " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the session state. The value returned must be one of the states in
+ * {@link SipSessionState}.
+ *
+ * @return the session state
+ */
+ public int getState() {
+ try {
+ return mSession.getState();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState(): " + e);
+ return State.NOT_DEFINED;
+ }
+ }
+
+ /**
+ * Checks if the session is in a call.
+ *
+ * @return true if the session is in a call
+ */
+ public boolean isInCall() {
+ try {
+ return mSession.isInCall();
+ } catch (RemoteException e) {
+ Log.e(TAG, "isInCall(): " + e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets the call ID of the session.
+ *
+ * @return the call ID
+ */
+ public String getCallId() {
+ try {
+ return mSession.getCallId();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getCallId(): " + e);
+ return null;
+ }
+ }
+
+
+ /**
+ * Sets the listener to listen to the session events. A {@code SipSession}
+ * can only hold one listener at a time. Subsequent calls to this method
+ * override the previous listener.
+ *
+ * @param listener to listen to the session events of this object
+ */
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+
+ /**
+ * Performs registration to the server specified by the associated local
+ * profile. The session listener is called back upon success or failure of
+ * registration. The method is only valid to call when the session state is
+ * in {@link SipSessionState#READY_TO_CALL}.
+ *
+ * @param duration duration in second before the registration expires
+ * @see Listener
+ */
+ public void register(int duration) {
+ try {
+ mSession.register(duration);
+ } catch (RemoteException e) {
+ Log.e(TAG, "register(): " + e);
+ }
+ }
+
+ /**
+ * Performs unregistration to the server specified by the associated local
+ * profile. Unregistration is technically the same as registration with zero
+ * expiration duration. The session listener is called back upon success or
+ * failure of unregistration. The method is only valid to call when the
+ * session state is in {@link SipSessionState#READY_TO_CALL}.
+ *
+ * @see Listener
+ */
+ public void unregister() {
+ try {
+ mSession.unregister();
+ } catch (RemoteException e) {
+ Log.e(TAG, "unregister(): " + e);
+ }
+ }
+
+ /**
+ * Initiates a call to the specified profile. The session listener is called
+ * back upon defined session events. The method is only valid to call when
+ * the session state is in {@link SipSessionState#READY_TO_CALL}.
+ *
+ * @param callee the SIP profile to make the call to
+ * @param sessionDescription the session description of this call
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ * @see Listener
+ */
+ public void makeCall(SipProfile callee, String sessionDescription,
+ int timeout) {
+ try {
+ mSession.makeCall(callee, sessionDescription, timeout);
+ } catch (RemoteException e) {
+ Log.e(TAG, "makeCall(): " + e);
+ }
+ }
+
+ /**
+ * Answers an incoming call with the specified session description. The
+ * method is only valid to call when the session state is in
+ * {@link SipSessionState#INCOMING_CALL}.
+ *
+ * @param sessionDescription the session description to answer this call
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ */
+ public void answerCall(String sessionDescription, int timeout) {
+ try {
+ mSession.answerCall(sessionDescription, timeout);
+ } catch (RemoteException e) {
+ Log.e(TAG, "answerCall(): " + e);
+ }
+ }
+
+ /**
+ * Ends an established call, terminates an outgoing call or rejects an
+ * incoming call. The method is only valid to call when the session state is
+ * in {@link SipSessionState#IN_CALL},
+ * {@link SipSessionState#INCOMING_CALL},
+ * {@link SipSessionState#OUTGOING_CALL} or
+ * {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
+ */
+ public void endCall() {
+ try {
+ mSession.endCall();
+ } catch (RemoteException e) {
+ Log.e(TAG, "endCall(): " + e);
+ }
+ }
+
+ /**
+ * Changes the session description during a call. The method is only valid
+ * to call when the session state is in {@link SipSessionState#IN_CALL}.
+ *
+ * @param sessionDescription the new session description
+ * @param timeout the session will be timed out if the call is not
+ * established within {@code timeout} seconds. Default value (defined
+ * by SIP protocol) is used if {@code timeout} is zero or negative.
+ */
+ public void changeCall(String sessionDescription, int timeout) {
+ try {
+ mSession.changeCall(sessionDescription, timeout);
+ } catch (RemoteException e) {
+ Log.e(TAG, "changeCall(): " + e);
+ }
+ }
+
+ ISipSession getRealSession() {
+ return mSession;
+ }
+
+ private ISipSessionListener createListener() {
+ return new ISipSessionListener.Stub() {
+ public void onCalling(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCalling(SipSession.this);
+ }
+ }
+
+ public void onRinging(ISipSession session, SipProfile caller,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onRinging(SipSession.this, caller,
+ sessionDescription);
+ }
+ }
+
+ public void onRingingBack(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRingingBack(SipSession.this);
+ }
+ }
+
+ public void onCallEstablished(ISipSession session,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onCallEstablished(SipSession.this,
+ sessionDescription);
+ }
+ }
+
+ public void onCallEnded(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCallEnded(SipSession.this);
+ }
+ }
+
+ public void onCallBusy(ISipSession session) {
+ if (mListener != null) {
+ mListener.onCallBusy(SipSession.this);
+ }
+ }
+
+ public void onCallChangeFailed(ISipSession session, int errorCode,
+ String message) {
+ if (mListener != null) {
+ mListener.onCallChangeFailed(SipSession.this, errorCode,
+ message);
+ }
+ }
+
+ public void onError(ISipSession session, int errorCode, String message) {
+ if (mListener != null) {
+ mListener.onError(SipSession.this, errorCode, message);
+ }
+ }
+
+ public void onRegistering(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRegistering(SipSession.this);
+ }
+ }
+
+ public void onRegistrationDone(ISipSession session, int duration) {
+ if (mListener != null) {
+ mListener.onRegistrationDone(SipSession.this, duration);
+ }
+ }
+
+ public void onRegistrationFailed(ISipSession session, int errorCode,
+ String message) {
+ if (mListener != null) {
+ mListener.onRegistrationFailed(SipSession.this, errorCode,
+ message);
+ }
+ }
+
+ public void onRegistrationTimeout(ISipSession session) {
+ if (mListener != null) {
+ mListener.onRegistrationTimeout(SipSession.this);
+ }
+ }
+ };
+ }
+}
diff --git a/voip/java/android/net/sip/SipSessionState.java b/voip/java/android/net/sip/SipSessionState.java
deleted file mode 100644
index 31e9d3f..0000000
--- a/voip/java/android/net/sip/SipSessionState.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Defines {@link ISipSession} states.
- * @hide
- */
-public class SipSessionState {
- /** When session is ready to initiate a call or transaction. */
- public static final int READY_TO_CALL = 0;
-
- /** When the registration request is sent out. */
- public static final int REGISTERING = 1;
-
- /** When the unregistration request is sent out. */
- public static final int DEREGISTERING = 2;
-
- /** When an INVITE request is received. */
- public static final int INCOMING_CALL = 3;
-
- /** When an OK response is sent for the INVITE request received. */
- public static final int INCOMING_CALL_ANSWERING = 4;
-
- /** When an INVITE request is sent. */
- public static final int OUTGOING_CALL = 5;
-
- /** When a RINGING response is received for the INVITE request sent. */
- public static final int OUTGOING_CALL_RING_BACK = 6;
-
- /** When a CANCEL request is sent for the INVITE request sent. */
- public static final int OUTGOING_CALL_CANCELING = 7;
-
- /** When a call is established. */
- public static final int IN_CALL = 8;
-
- /** Some error occurs when making a remote call to {@link ISipSession}. */
- public static final int REMOTE_ERROR = 9;
-
- /** When an OPTIONS request is sent. */
- public static final int PINGING = 10;
-
- /** Not defined. */
- public static final int NOT_DEFINED = 101;
-
- /**
- * Converts the state to string.
- */
- public static String toString(int state) {
- switch (state) {
- case READY_TO_CALL:
- return "READY_TO_CALL";
- case REGISTERING:
- return "REGISTERING";
- case DEREGISTERING:
- return "DEREGISTERING";
- case INCOMING_CALL:
- return "INCOMING_CALL";
- case INCOMING_CALL_ANSWERING:
- return "INCOMING_CALL_ANSWERING";
- case OUTGOING_CALL:
- return "OUTGOING_CALL";
- case OUTGOING_CALL_RING_BACK:
- return "OUTGOING_CALL_RING_BACK";
- case OUTGOING_CALL_CANCELING:
- return "OUTGOING_CALL_CANCELING";
- case IN_CALL:
- return "IN_CALL";
- case REMOTE_ERROR:
- return "REMOTE_ERROR";
- case PINGING:
- return "PINGING";
- default:
- return "NOT_DEFINED";
- }
- }
-
- private SipSessionState() {
- }
-}