-- cgit v1.2.3 From 98cee0ce2354234e72bafb836864ec10a490ea4d Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 5 Aug 2010 10:21:20 +0800 Subject: Move the sip related codes to framework. Change-Id: Ib81dadc39b73325c8438f078c7251857a83834fe --- java/android/net/rtp/AudioCodec.java | 56 ++ java/android/net/rtp/AudioGroup.java | 91 +++ java/android/net/rtp/AudioStream.java | 136 +++++ java/android/net/rtp/RtpStream.java | 173 ++++++ java/android/net/sip/BinderHelper.java | 79 +++ java/android/net/sip/ISipService.aidl | 42 ++ java/android/net/sip/ISipSession.aidl | 147 +++++ java/android/net/sip/ISipSessionListener.aidl | 126 ++++ java/android/net/sip/SdpSessionDescription.java | 428 +++++++++++++ java/android/net/sip/SessionDescription.aidl | 19 + java/android/net/sip/SessionDescription.java | 83 +++ java/android/net/sip/SipAudioCall.java | 306 ++++++++++ java/android/net/sip/SipAudioCallImpl.java | 701 ++++++++++++++++++++++ java/android/net/sip/SipManager.java | 499 +++++++++++++++ java/android/net/sip/SipProfile.aidl | 19 + java/android/net/sip/SipProfile.java | 401 +++++++++++++ java/android/net/sip/SipRegistrationListener.java | 48 ++ java/android/net/sip/SipSessionAdapter.java | 67 +++ java/android/net/sip/SipSessionState.java | 66 ++ 19 files changed, 3487 insertions(+) create mode 100644 java/android/net/rtp/AudioCodec.java create mode 100644 java/android/net/rtp/AudioGroup.java create mode 100644 java/android/net/rtp/AudioStream.java create mode 100644 java/android/net/rtp/RtpStream.java create mode 100644 java/android/net/sip/BinderHelper.java create mode 100644 java/android/net/sip/ISipService.aidl create mode 100644 java/android/net/sip/ISipSession.aidl create mode 100644 java/android/net/sip/ISipSessionListener.aidl create mode 100644 java/android/net/sip/SdpSessionDescription.java create mode 100644 java/android/net/sip/SessionDescription.aidl create mode 100644 java/android/net/sip/SessionDescription.java create mode 100644 java/android/net/sip/SipAudioCall.java create mode 100644 java/android/net/sip/SipAudioCallImpl.java create mode 100644 java/android/net/sip/SipManager.java create mode 100644 java/android/net/sip/SipProfile.aidl create mode 100644 java/android/net/sip/SipProfile.java create mode 100644 java/android/net/sip/SipRegistrationListener.java create mode 100644 java/android/net/sip/SipSessionAdapter.java create mode 100644 java/android/net/sip/SipSessionState.java diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java new file mode 100644 index 0000000..89e6aa9 --- /dev/null +++ b/java/android/net/rtp/AudioCodec.java @@ -0,0 +1,56 @@ +/* + * 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.rtp; + +/** @hide */ +public class AudioCodec { + public static final AudioCodec ULAW = new AudioCodec("PCMU", 8000, 160, 0); + public static final AudioCodec ALAW = new AudioCodec("PCMA", 8000, 160, 8); + + /** + * Returns system supported codecs. + */ + public static AudioCodec[] getSystemSupportedCodecs() { + return new AudioCodec[] {AudioCodec.ULAW, AudioCodec.ALAW}; + } + + /** + * Returns the codec instance if it is supported by the system. + * + * @param name name of the codec + * @return the matched codec or null if the codec name is not supported by + * the system + */ + public static AudioCodec getSystemSupportedCodec(String name) { + for (AudioCodec codec : getSystemSupportedCodecs()) { + if (codec.name.equals(name)) return codec; + } + return null; + } + + public final String name; + public final int sampleRate; + public final int sampleCount; + public final int defaultType; + + private AudioCodec(String name, int sampleRate, int sampleCount, int defaultType) { + this.name = name; + this.sampleRate = sampleRate; + this.sampleCount = sampleCount; + this.defaultType = defaultType; + } +} diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java new file mode 100644 index 0000000..dc86082 --- /dev/null +++ b/java/android/net/rtp/AudioGroup.java @@ -0,0 +1,91 @@ +/* + * 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.rtp; + +import java.util.HashMap; +import java.util.Map; + +/** + */ +/** @hide */ +public class AudioGroup { + public static final int MODE_ON_HOLD = 0; + public static final int MODE_MUTED = 1; + public static final int MODE_NORMAL = 2; + public static final int MODE_EC_ENABLED = 3; + + private final Map mStreams; + private int mMode = MODE_ON_HOLD; + + private int mNative; + static { + System.loadLibrary("rtp_jni"); + } + + public AudioGroup() { + mStreams = new HashMap(); + } + + public int getMode() { + return mMode; + } + + public synchronized native void setMode(int mode); + + synchronized void add(AudioStream stream, AudioCodec codec, int codecType, int dtmfType) { + if (!mStreams.containsKey(stream)) { + try { + int id = add(stream.getMode(), stream.dup(), + stream.getRemoteAddress().getHostAddress(), stream.getRemotePort(), + codec.name, codec.sampleRate, codec.sampleCount, codecType, dtmfType); + mStreams.put(stream, id); + } catch (NullPointerException e) { + throw new IllegalStateException(e); + } + } + } + + private native int add(int mode, int socket, String remoteAddress, int remotePort, + String codecName, int sampleRate, int sampleCount, int codecType, int dtmfType); + + synchronized void remove(AudioStream stream) { + Integer id = mStreams.remove(stream); + if (id != null) { + remove(id); + } + } + + private native void remove(int id); + + /** + * Sends a DTMF digit to every {@link AudioStream} in this group. Currently + * only event {@code 0} to {@code 15} are supported. + * + * @throws IllegalArgumentException if the event is invalid. + */ + public native synchronized void sendDtmf(int event); + + public synchronized void reset() { + remove(-1); + } + + @Override + protected void finalize() throws Throwable { + reset(); + super.finalize(); + } +} diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java new file mode 100644 index 0000000..a955fd2 --- /dev/null +++ b/java/android/net/rtp/AudioStream.java @@ -0,0 +1,136 @@ +/* + * 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.rtp; + +import java.net.InetAddress; +import java.net.SocketException; + +/** + * AudioStream represents a RTP stream carrying audio payloads. + */ +/** @hide */ +public class AudioStream extends RtpStream { + private AudioCodec mCodec; + private int mCodecType = -1; + private int mDtmfType = -1; + private AudioGroup mGroup; + + /** + * Creates an AudioStream on the given local address. Note that the local + * port is assigned automatically to conform with RFC 3550. + * + * @param address The network address of the local host to bind to. + * @throws SocketException if the address cannot be bound or a problem + * occurs during binding. + */ + public AudioStream(InetAddress address) throws SocketException { + super(address); + } + + /** + * Returns {@code true} if the stream already joined an {@link AudioGroup}. + */ + @Override + public final boolean isBusy() { + return mGroup != null; + } + + /** + * Returns the joined {@link AudioGroup}. + */ + public AudioGroup getAudioGroup() { + return mGroup; + } + + /** + * Joins an {@link AudioGroup}. Each stream can join only one group at a + * time. The group can be changed by passing a different one or removed + * by calling this method with {@code null}. + * + * @param group The AudioGroup to join or {@code null} to leave. + * @throws IllegalStateException if the stream is not properly configured. + * @see AudioGroup + */ + public void join(AudioGroup group) { + if (mGroup == group) { + return; + } + if (mGroup != null) { + mGroup.remove(this); + mGroup = null; + } + if (group != null) { + group.add(this, mCodec, mCodecType, mDtmfType); + mGroup = group; + } + } + + /** + * Sets the {@link AudioCodec} and its RTP payload type. According to RFC + * 3551, the type must be in the range of 0 and 127, where 96 and above are + * dynamic types. For codecs with static mappings (non-negative + * {@link AudioCodec#defaultType}), assigning a different non-dynamic type + * is disallowed. + * + * @param codec The AudioCodec to be used. + * @param type The RTP payload type. + * @throws IllegalArgumentException if the type is invalid or used by DTMF. + * @throws IllegalStateException if the stream is busy. + */ + public void setCodec(AudioCodec codec, int type) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + if (type < 0 || type > 127 || (type != codec.defaultType && type < 96)) { + throw new IllegalArgumentException("Invalid type"); + } + if (type == mDtmfType) { + throw new IllegalArgumentException("The type is used by DTMF"); + } + mCodec = codec; + mCodecType = type; + } + + /** + * Sets the RTP payload type for dual-tone multi-frequency (DTMF) digits. + * The primary usage is to send digits to the remote gateway to perform + * certain tasks, such as second-stage dialing. According to RFC 2833, the + * RTP payload type for DTMF is assigned dynamically, so it must be in the + * range of 96 and 127. One can use {@code -1} to disable DTMF and free up + * the previous assigned value. This method cannot be called when the stream + * already joined an {@link AudioGroup}. + * + * @param type The RTP payload type to be used or {@code -1} to disable it. + * @throws IllegalArgumentException if the type is invalid or used by codec. + * @throws IllegalStateException if the stream is busy. + * @see AudioGroup#sendDtmf(int) + */ + public void setDtmfType(int type) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + if (type != -1) { + if (type < 96 || type > 127) { + throw new IllegalArgumentException("Invalid type"); + } + if (type == mCodecType) { + throw new IllegalArgumentException("The type is used by codec"); + } + } + mDtmfType = type; + } +} diff --git a/java/android/net/rtp/RtpStream.java b/java/android/net/rtp/RtpStream.java new file mode 100644 index 0000000..ef5ca17 --- /dev/null +++ b/java/android/net/rtp/RtpStream.java @@ -0,0 +1,173 @@ +/* + * 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.rtp; + +import java.net.InetAddress; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.SocketException; + +/** + * RtpStream represents a base class of media streams running over + * Real-time Transport Protocol (RTP). + */ +/** @hide */ +public class RtpStream { + public static final int MODE_NORMAL = 0; + public static final int MODE_SEND_ONLY = 1; + public static final int MODE_RECEIVE_ONLY = 2; + + private final InetAddress mLocalAddress; + private final int mLocalPort; + + private InetAddress mRemoteAddress; + private int mRemotePort = -1; + private int mMode = MODE_NORMAL; + + private int mNative; + static { + System.loadLibrary("rtp_jni"); + } + + /** + * Creates a RtpStream on the given local address. Note that the local + * port is assigned automatically to conform with RFC 3550. + * + * @param address The network address of the local host to bind to. + * @throws SocketException if the address cannot be bound or a problem + * occurs during binding. + */ + RtpStream(InetAddress address) throws SocketException { + mLocalPort = create(address.getHostAddress()); + mLocalAddress = address; + } + + private native int create(String address) throws SocketException; + + /** + * Returns the network address of the local host. + */ + public InetAddress getLocalAddress() { + return mLocalAddress; + } + + /** + * Returns the network port of the local host. + */ + public int getLocalPort() { + return mLocalPort; + } + + /** + * Returns the network address of the remote host or {@code null} if the + * stream is not associated. + */ + public InetAddress getRemoteAddress() { + return mRemoteAddress; + } + + /** + * Returns the network port of the remote host or {@code -1} if the stream + * is not associated. + */ + public int getRemotePort() { + return mRemotePort; + } + + /** + * Returns {@code true} if the stream is busy. This method is intended to be + * overridden by subclasses. + */ + public boolean isBusy() { + return false; + } + + /** + * Returns the current mode. The initial mode is {@link #MODE_NORMAL}. + */ + public int getMode() { + return mMode; + } + + /** + * Changes the current mode. It must be one of {@link #MODE_NORMAL}, + * {@link #MODE_SEND_ONLY}, and {@link #MODE_RECEIVE_ONLY}. + * + * @param mode The mode to change to. + * @throws IllegalArgumentException if the mode is invalid. + * @throws IllegalStateException if the stream is busy. + * @see #isBusy() + */ + public void setMode(int mode) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + if (mode != MODE_NORMAL && mode != MODE_SEND_ONLY && mode != MODE_RECEIVE_ONLY) { + throw new IllegalArgumentException("Invalid mode"); + } + mMode = mode; + } + + /** + * Associates with a remote host. + * + * @param address The network address of the remote host. + * @param port The network port of the remote host. + * @throws IllegalArgumentException if the address is not supported or the + * port is invalid. + * @throws IllegalStateException if the stream is busy. + * @see #isBusy() + */ + public void associate(InetAddress address, int port) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + if (!(address instanceof Inet4Address && mLocalAddress instanceof Inet4Address) && + !(address instanceof Inet6Address && mLocalAddress instanceof Inet6Address)) { + throw new IllegalArgumentException("Unsupported address"); + } + if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Invalid port"); + } + mRemoteAddress = address; + mRemotePort = port; + } + + synchronized native int dup(); + + /** + * Releases allocated resources. The stream becomes inoperable after calling + * this method. + * + * @throws IllegalStateException if the stream is busy. + * @see #isBusy() + */ + public void release() { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + close(); + } + + private synchronized native void close(); + + @Override + protected void finalize() throws Throwable { + close(); + super.finalize(); + } +} diff --git a/java/android/net/sip/BinderHelper.java b/java/android/net/sip/BinderHelper.java new file mode 100644 index 0000000..bd3da32 --- /dev/null +++ b/java/android/net/sip/BinderHelper.java @@ -0,0 +1,79 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.ConditionVariable; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Looper; +import android.util.Log; + +// TODO: throw away this class after moving SIP classes to framework +// This class helps to get IBinder instance of a service in a blocking call. +// The method cannot be called in app's main thread as the ServiceConnection +// callback will. +class BinderHelper { + private Context mContext; + private IBinder mBinder; + private Class mClass; + + BinderHelper(Context context, Class klass) { + mContext = context; + mClass = klass; + } + + void startService() { + mContext.startService(new Intent(mClass.getName())); + } + + void stopService() { + mContext.stopService(new Intent(mClass.getName())); + } + + IBinder getBinder() { + // cannot call this method in app's main thread + if (Looper.getMainLooper().getThread() == Thread.currentThread()) { + throw new RuntimeException( + "This method cannot be called in app's main thread"); + } + + final ConditionVariable cv = new ConditionVariable(); + cv.close(); + ServiceConnection c = new ServiceConnection() { + public synchronized void onServiceConnected( + ComponentName className, IBinder binder) { + Log.v("BinderHelper", "service connected!"); + mBinder = binder; + cv.open(); + mContext.unbindService(this); + } + + public void onServiceDisconnected(ComponentName className) { + cv.open(); + mContext.unbindService(this); + } + }; + if (mContext.bindService(new Intent(mClass.getName()), c, 0)) { + cv.block(4500); + } + return mBinder; + } +} diff --git a/java/android/net/sip/ISipService.aidl b/java/android/net/sip/ISipService.aidl new file mode 100644 index 0000000..6c68213 --- /dev/null +++ b/java/android/net/sip/ISipService.aidl @@ -0,0 +1,42 @@ +/* + * 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.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SipProfile; + +/** + * {@hide} + */ +interface ISipService { + void open(in SipProfile localProfile); + void open3(in SipProfile localProfile, + String incomingCallBroadcastAction, + in ISipSessionListener listener); + void close(in String localProfileUri); + boolean isOpened(String localProfileUri); + boolean isRegistered(String localProfileUri); + void setRegistrationListener(String localProfileUri, + ISipSessionListener listener); + + ISipSession createSession(in SipProfile localProfile, + in ISipSessionListener listener); + ISipSession getPendingSession(String callId); + + SipProfile[] getListOfProfiles(); +} diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl new file mode 100644 index 0000000..fbcb056 --- /dev/null +++ b/java/android/net/sip/ISipSession.aidl @@ -0,0 +1,147 @@ +/* + * 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.net.sip.ISipSessionListener; +import android.net.sip.SessionDescription; +import android.net.sip.SipProfile; + +/** + * A SIP session that is associated with a SIP dialog or a transaction + * (e.g., registration) not within a dialog. + * @hide + */ +interface ISipSession { + /** + * Gets the IP address of the local host on which this SIP session runs. + * + * @return the IP address of the local host + */ + String getLocalIp(); + + /** + * Gets the SIP profile that this session is associated with. + * + * @return the SIP profile that this session is associated with + */ + SipProfile getLocalProfile(); + + /** + * 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 + */ + SipProfile getPeerProfile(); + + /** + * Gets the session state. The value returned must be one of the states in + * {@link SipSessionState}. One may convert it to {@link SipSessionState} by + * + * Enum.valueOf(SipSessionState.class, session.getState()); + * + * + * @return the session state + */ + String getState(); + + /** + * Checks if the session is in a call. + * + * @return true if the session is in a call + */ + boolean isInCall(); + + /** + * Gets the call ID of the session. + * + * @return the call ID + */ + String getCallId(); + + + /** + * Sets the listener to listen to the session events. A {@link ISipSession} + * 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 + */ + void setListener(in ISipSessionListener 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 ISipSessionListener + */ + void register(int duration); + + /** + * 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 ISipSessionListener + */ + void unregister(); + + /** + * 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 + * @see ISipSessionListener + */ + void makeCall(in SipProfile callee, + in SessionDescription sessionDescription); + + /** + * 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 + */ + void answerCall(in SessionDescription sessionDescription); + + /** + * 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}. + */ + void endCall(); + + /** + * 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 + */ + void changeCall(in SessionDescription sessionDescription); +} diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl new file mode 100644 index 0000000..8570958 --- /dev/null +++ b/java/android/net/sip/ISipSessionListener.aidl @@ -0,0 +1,126 @@ +/* + * 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.net.sip.ISipSession; +import android.net.sip.SipProfile; + +/** + * Listener class to listen to SIP session events. + * @hide + */ +interface ISipSessionListener { + /** + * Called when an INVITE request is sent to initiate a new call. + * + * @param session the session object that carries out the transaction + */ + void onCalling(in ISipSession 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 + */ + void onRinging(in ISipSession session, in SipProfile caller, + in byte[] sessionDescription); + + /** + * Called when a RINGING response is received for the INVITE request sent + * + * @param session the session object that carries out the transaction + */ + void onRingingBack(in ISipSession 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 + */ + void onCallEstablished(in ISipSession session, + in byte[] sessionDescription); + + /** + * Called when the session is terminated. + * + * @param session the session object that is associated with the dialog + */ + void onCallEnded(in ISipSession session); + + /** + * Called when the peer is busy during session initialization. + * + * @param session the session object that carries out the transaction + */ + void onCallBusy(in ISipSession session); + + /** + * Called when an error occurs during session initialization and + * termination. + * + * @param session the session object that carries out the transaction + * @param errorClass name of the exception class + * @param errorMessage error message + */ + void onError(in ISipSession session, String errorClass, + String errorMessage); + + /** + * Called when an error occurs during session modification negotiation. + * + * @param session the session object that carries out the transaction + * @param errorClass name of the exception class + * @param errorMessage error message + */ + void onCallChangeFailed(in ISipSession session, String errorClass, + String errorMessage); + + /** + * Called when a registration request is sent. + * + * @param session the session object that carries out the transaction + */ + void onRegistering(in ISipSession 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 + */ + void onRegistrationDone(in ISipSession session, int duration); + + /** + * Called when the registration fails. + * + * @param session the session object that carries out the transaction + * @param errorClass name of the exception class + * @param errorMessage error message + */ + void onRegistrationFailed(in ISipSession session, String errorClass, + String errorMessage); + + /** + * Called when the registration gets timed out. + * + * @param session the session object that carries out the transaction + */ + void onRegistrationTimeout(in ISipSession session); +} diff --git a/java/android/net/sip/SdpSessionDescription.java b/java/android/net/sip/SdpSessionDescription.java new file mode 100644 index 0000000..0c29935 --- /dev/null +++ b/java/android/net/sip/SdpSessionDescription.java @@ -0,0 +1,428 @@ +/* + * 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 RFC 4566. + * @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 typeVector = new Vector(); + 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 SdpSessionDescription build() { + return mSdp; + } + } + + 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 descriptions = (Vector) + 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 v = (Vector) + 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 getAudioCodecs() { + MediaDescription md = getMediaDescription(AUDIO); + if (md == null) return new ArrayList(); + + // 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 codecs = new ArrayList(); + Vector v = (Vector) + 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 descriptions = (Vector) + 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/java/android/net/sip/SessionDescription.aidl b/java/android/net/sip/SessionDescription.aidl new file mode 100644 index 0000000..a120d16 --- /dev/null +++ b/java/android/net/sip/SessionDescription.aidl @@ -0,0 +1,19 @@ +/* + * 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/java/android/net/sip/SessionDescription.java b/java/android/net/sip/SessionDescription.java new file mode 100644 index 0000000..d476f0b --- /dev/null +++ b/java/android/net/sip/SessionDescription.java @@ -0,0 +1,83 @@ +/* + * 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 CREATOR = + new Parcelable.Creator() { + 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/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java new file mode 100644 index 0000000..abdc9d7 --- /dev/null +++ b/java/android/net/sip/SipAudioCall.java @@ -0,0 +1,306 @@ +/* + * 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.net.rtp.AudioGroup; +import android.net.rtp.AudioStream; +import android.os.Message; + +import javax.sip.SipException; + +/** + * Interface for making audio calls over SIP. + * @hide + */ +public interface SipAudioCall { + /** Listener class for all event callbacks. */ + public interface Listener { + /** + * Called when the call object is ready to make another call. + * + * @param call the call object that is ready to make another call + */ + void onReadyToCall(SipAudioCall call); + + /** + * Called when a request is sent out to initiate a new call. + * + * @param call the call object that carries out the audio call + */ + void onCalling(SipAudioCall call); + + /** + * Called when a new call comes in. + * + * @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); + + /** + * Called when a RINGING response is received for the INVITE request sent + * + * @param call the call object that carries out the audio call + */ + void onRingingBack(SipAudioCall call); + + /** + * Called when the session is established. + * + * @param call the call object that carries out the audio call + */ + void onCallEstablished(SipAudioCall call); + + /** + * Called when the session is terminated. + * + * @param call the call object that carries out the audio call + */ + void onCallEnded(SipAudioCall call); + + /** + * Called when the peer is busy during session initialization. + * + * @param call the call object that carries out the audio call + */ + void onCallBusy(SipAudioCall call); + + /** + * Called when the call is on hold. + * + * @param call the call object that carries out the audio call + */ + void onCallHeld(SipAudioCall call); + + /** + * Called when an error occurs. + * + * @param call the call object that carries out the audio call + * @param errorMessage error message + */ + void onError(SipAudioCall call, String errorMessage); + } + + /** + * The adapter class for {@link SipAudioCall#Listener}. The default + * implementation of all callback methods is no-op. + */ + 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, String errorMessage) { + onChanged(call); + } + } + + /** + * Sets the listener to listen to the audio call events. The method calls + * {@link #setListener(Listener, false)}. + * + * @param listener to listen to the audio call events of this object + * @see #setListener(Listener, boolean) + */ + void setListener(Listener listener); + + /** + * Sets the listener to listen to the audio call events. A + * {@link SipAudioCall} can only hold one listener at a time. Subsequent + * calls to this method override the previous listener. + * + * @param listener to listen to the audio call events of this object + * @param callbackImmediately set to true if the caller wants to be called + * back immediately on the current state + */ + void setListener(Listener listener, boolean callbackImmediately); + + /** + * Closes this object. The object is not usable after being closed. + */ + void close(); + + /** + * Initiates an audio call to the specified profile. + * + * @param callee the SIP profile to make the call to + * @param sipManager the {@link SipManager} object to help make call with + */ + void makeCall(SipProfile callee, SipManager sipManager) throws SipException; + + /** + * Attaches an incoming call to this call object. + * + * @param session the session that receives the incoming call + * @param sdp the session description of the incoming call + */ + void attachCall(ISipSession session, SdpSessionDescription sdp) + throws SipException; + + /** Ends a call. */ + void endCall() throws SipException; + + /** + * Puts a call on hold. When succeeds, + * {@link #Listener#onCallHeld(SipAudioCall)} is called. + */ + void holdCall() throws SipException; + + /** Answers a call. */ + void answerCall() throws SipException; + + /** + * Continues a call that's on hold. When succeeds, + * {@link #Listener#onCallEstablished(SipAudioCall)} is called. + */ + void continueCall() throws SipException; + + /** Puts the device to in-call mode. */ + void setInCallMode(); + + /** Puts the device to speaker mode. */ + void setSpeakerMode(); + + /** Toggles mute. */ + void toggleMute(); + + /** + * Checks if the call is on hold. + * + * @return true if the call is on hold + */ + boolean isOnHold(); + + /** + * Checks if the call is muted. + * + * @return true if the call is muted + */ + boolean isMuted(); + + /** + * Sends a DTMF code. + * + * @param code the DTMF code to send + */ + void sendDtmf(int code); + + /** + * Sends a DTMF code. + * + * @param code the DTMF code to send + * @param result the result message to send when done + */ + void sendDtmf(int code, Message result); + + /** + * Gets the {@link AudioStream} object used in this call. The object + * represents the RTP stream that carries the audio data to and from the + * peer. The object may not be created before the call is established. And + * it is undefined after the call ends or the {@link #close} method is + * called. + * + * @return the {@link AudioStream} object or null if the RTP stream has not + * yet been set up + */ + AudioStream getAudioStream(); + + /** + * Gets the {@link AudioGroup} object which the {@link AudioStream} object + * joins. The group object may not exist before the call is established. + * Also, the {@code AudioStream} may change its group during a call (e.g., + * after the call is held/un-held). Finally, the {@code AudioGroup} object + * returned by this method is undefined after the call ends or the + * {@link #close} method is called. + * + * @return the {@link AudioGroup} object or null if the RTP stream has not + * yet been set up + * @see #getAudioStream + */ + AudioGroup getAudioGroup(); + + /** + * Checks if the call is established. + * + * @return true if the call is established + */ + boolean isInCall(); + + /** + * Gets the local SIP profile. + * + * @return the local SIP profile + */ + SipProfile getLocalProfile(); + + /** + * Gets the peer's SIP profile. + * + * @return the peer's SIP profile + */ + SipProfile getPeerProfile(); + + /** + * Gets the state of the {@link ISipSession} that carries this call. + * + * @return the session state + */ + SipSessionState getState(); + + /** + * 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); + + /** + * Enables/disables the ring tone. + * + * @param enabled true to enable; false to disable + */ + void setRingtoneEnabled(boolean enabled); +} diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java new file mode 100644 index 0000000..57e0bd2 --- /dev/null +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -0,0 +1,701 @@ +/* + * 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.fields.SDPKeywords; + +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.ISipSession; +import android.net.sip.SdpSessionDescription; +import android.net.sip.SessionDescription; +import android.net.sip.SipAudioCall; +import android.net.sip.SipManager; +import android.net.sip.SipProfile; +import android.net.sip.SipSessionAdapter; +import android.net.sip.SipSessionState; +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.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.sdp.SdpException; +import javax.sip.SipException; + +/** + * 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 String AUDIO = "audio"; + private static final int DTMF = 101; + + private Context mContext; + private SipProfile mLocalProfile; + private SipAudioCall.Listener mListener; + private ISipSession mSipSession; + private SdpSessionDescription mPeerSd; + + private AudioStream mRtpSession; + private SdpSessionDescription.AudioCodec mCodec; + private long mSessionId = -1L; // SDP session ID + 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; + + public SipAudioCallImpl(Context context, SipProfile localProfile) { + mContext = context; + mLocalProfile = localProfile; + } + + public void setListener(SipAudioCall.Listener listener) { + setListener(listener, false); + } + + public void setListener(SipAudioCall.Listener listener, + boolean callbackImmediately) { + mListener = listener; + if ((listener == null) || !callbackImmediately) return; + try { + SipSessionState state = getState(); + switch (state) { + case READY_TO_CALL: + listener.onReadyToCall(this); + break; + case INCOMING_CALL: + listener.onRinging(this, getPeerProfile(mSipSession)); + startRinging(); + break; + case OUTGOING_CALL: + listener.onCalling(this); + break; + default: + listener.onError(this, "wrong state to attach call: " + state); + } + } 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(); + mSipSession = null; + mInCall = false; + mHold = false; + mSessionId = -1L; + } + + public synchronized SipProfile getLocalProfile() { + return mLocalProfile; + } + + public synchronized SipProfile getPeerProfile() { + try { + return (mSipSession == null) ? null : mSipSession.getPeerProfile(); + } catch (RemoteException e) { + return null; + } + } + + public synchronized SipSessionState getState() { + if (mSipSession == null) return SipSessionState.READY_TO_CALL; + try { + return Enum.valueOf(SipSessionState.class, 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(SipAudioCallImpl.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(SipAudioCallImpl.this); + } catch (Throwable t) { + Log.e(TAG, "onRingingBack()", t); + } + } + } + + @Override + public synchronized void onRinging(ISipSession session, + SipProfile peerProfile, byte[] sessionDescription) { + try { + if ((mSipSession == null) || !mInCall + || !session.getCallId().equals(mSipSession.getCallId())) { + // should not happen + session.endCall(); + return; + } + + // session changing request + try { + mPeerSd = new SdpSessionDescription(sessionDescription); + answerCall(); + } catch (Throwable e) { + Log.e(TAG, "onRinging()", e); + session.endCall(); + } + } catch (RemoteException e) { + Log.e(TAG, "onRinging()", e); + } + } + + private synchronized void establishCall(byte[] sessionDescription) { + stopRingbackTone(); + stopRinging(); + try { + SdpSessionDescription sd = + new SdpSessionDescription(sessionDescription); + Log.d(TAG, "sip call established: " + sd); + startCall(sd); + mInCall = true; + } catch (SdpException e) { + Log.e(TAG, "createSessionDescription()", e); + } + } + + @Override + public void onCallEstablished(ISipSession session, + byte[] sessionDescription) { + establishCall(sessionDescription); + Listener listener = mListener; + if (listener != null) { + try { + if (mHold) { + listener.onCallHeld(SipAudioCallImpl.this); + } else { + listener.onCallEstablished(SipAudioCallImpl.this); + } + } catch (Throwable t) { + Log.e(TAG, "onCallEstablished()", t); + } + } + } + + @Override + public void onCallEnded(ISipSession session) { + Log.d(TAG, "sip call ended: " + session); + close(); + Listener listener = mListener; + if (listener != null) { + try { + listener.onCallEnded(SipAudioCallImpl.this); + } catch (Throwable t) { + Log.e(TAG, "onCallEnded()", t); + } + } + } + + @Override + public void onCallBusy(ISipSession session) { + Log.d(TAG, "sip call busy: " + session); + close(false); + Listener listener = mListener; + if (listener != null) { + try { + listener.onCallBusy(SipAudioCallImpl.this); + } catch (Throwable t) { + Log.e(TAG, "onCallBusy()", t); + } + } + } + + @Override + public void onCallChangeFailed(ISipSession session, + String className, String message) { + Log.d(TAG, "sip call change failed: " + message); + Listener listener = mListener; + if (listener != null) { + try { + listener.onError(SipAudioCallImpl.this, + className + ": " + message); + } catch (Throwable t) { + Log.e(TAG, "onCallBusy()", t); + } + } + } + + @Override + public void onError(ISipSession session, String className, + String message) { + Log.d(TAG, "sip session error: " + className + ": " + message); + synchronized (this) { + if (!isInCall()) close(true); + } + Listener listener = mListener; + if (listener != null) { + try { + listener.onError(SipAudioCallImpl.this, + className + ": " + message); + } catch (Throwable t) { + Log.e(TAG, "onError()", t); + } + } + } + + public synchronized void attachCall(ISipSession session, + SdpSessionDescription sdp) throws SipException { + mSipSession = session; + mPeerSd = sdp; + try { + session.setListener(this); + } catch (Throwable e) { + Log.e(TAG, "attachCall()", e); + throwSipException(e); + } + } + + public synchronized void makeCall(SipProfile peerProfile, + SipManager sipManager) throws SipException { + try { + mSipSession = sipManager.createSipSession(mLocalProfile, this); + if (mSipSession == null) { + throw new SipException( + "Failed to create SipSession; network available?"); + } + mSipSession.makeCall(peerProfile, createOfferSessionDescription()); + } catch (Throwable e) { + if (e instanceof SipException) { + throw (SipException) e; + } else { + throwSipException(e); + } + } + } + + public synchronized void endCall() throws SipException { + try { + stopRinging(); + if (mSipSession != null) mSipSession.endCall(); + stopCall(true); + } catch (Throwable e) { + throwSipException(e); + } + } + + public synchronized void holdCall() throws SipException { + if (mHold) return; + try { + mSipSession.changeCall(createHoldSessionDescription()); + mHold = true; + } catch (Throwable e) { + throwSipException(e); + } + + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } + + public synchronized void answerCall() throws SipException { + try { + stopRinging(); + mSipSession.answerCall(createAnswerSessionDescription()); + } catch (Throwable e) { + Log.e(TAG, "answerCall()", e); + throwSipException(e); + } + } + + public synchronized void continueCall() throws SipException { + if (!mHold) return; + try { + mHold = false; + mSipSession.changeCall(createContinueSessionDescription()); + } catch (Throwable e) { + throwSipException(e); + } + + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); + } + + private SessionDescription createOfferSessionDescription() { + AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs(); + return createSdpBuilder(true, convert(codecs)).build(); + } + + private SessionDescription createAnswerSessionDescription() { + try { + // choose an acceptable media from mPeerSd to answer + SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd); + SdpSessionDescription.Builder sdpBuilder = + createSdpBuilder(false, codec); + if (mPeerSd.isSendOnly(AUDIO)) { + sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null); + } else if (mPeerSd.isReceiveOnly(AUDIO)) { + sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null); + } + return sdpBuilder.build(); + } catch (SdpException e) { + throw new RuntimeException(e); + } + } + + private SessionDescription createHoldSessionDescription() { + try { + return createSdpBuilder(false, mCodec) + .addMediaAttribute(AUDIO, "sendonly", (String) null) + .build(); + } catch (SdpException e) { + throw new RuntimeException(e); + } + } + + private SessionDescription createContinueSessionDescription() { + return createSdpBuilder(true, mCodec).build(); + } + + private String getMediaDescription(SdpSessionDescription.AudioCodec codec) { + return String.format("%d %s/%d", codec.payloadType, codec.name, + codec.sampleRate); + } + + private long getSessionId() { + if (mSessionId < 0) { + mSessionId = System.currentTimeMillis(); + } + return mSessionId; + } + + private SdpSessionDescription.Builder createSdpBuilder( + boolean addTelephoneEvent, + SdpSessionDescription.AudioCodec... codecs) { + String localIp = getLocalIp(); + SdpSessionDescription.Builder sdpBuilder; + try { + long sessionVersion = System.currentTimeMillis(); + sdpBuilder = new SdpSessionDescription.Builder("SIP Call") + .setOrigin(mLocalProfile, getSessionId(), sessionVersion, + SDPKeywords.IN, SDPKeywords.IPV4, localIp) + .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4, + localIp); + List codecIds = new ArrayList(); + for (SdpSessionDescription.AudioCodec codec : codecs) { + codecIds.add(codec.payloadType); + } + if (addTelephoneEvent) codecIds.add(DTMF); + sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP", + codecIds.toArray(new Integer[codecIds.size()])); + for (SdpSessionDescription.AudioCodec codec : codecs) { + sdpBuilder.addMediaAttribute(AUDIO, "rtpmap", + getMediaDescription(codec)); + } + if (addTelephoneEvent) { + sdpBuilder.addMediaAttribute(AUDIO, "rtpmap", + DTMF + " telephone-event/8000"); + } + // FIXME: deal with vbr codec + sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20"); + } catch (SdpException e) { + throw new RuntimeException(e); + } + return sdpBuilder; + } + + 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 setInCallMode() { + ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .setSpeakerphoneOn(false); + ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .setMode(AudioManager.MODE_NORMAL); + } + + public synchronized void setSpeakerMode() { + ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .setSpeakerphoneOn(true); + } + + 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 mRtpSession; + } + + public synchronized AudioGroup getAudioGroup() { + return ((mRtpSession == null) ? null : mRtpSession.getAudioGroup()); + } + + private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) { + HashMap acceptableCodecs = + new HashMap(); + for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) { + acceptableCodecs.put(codec.name, codec); + } + for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) { + AudioCodec matchedCodec = acceptableCodecs.get(codec.name); + if (matchedCodec != null) return codec; + } + Log.w(TAG, "no common codec is found, use PCM/0"); + return convert(AudioCodec.ULAW); + } + + private AudioCodec convert(SdpSessionDescription.AudioCodec codec) { + AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name); + return ((c == null) ? AudioCodec.ULAW : c); + } + + private SdpSessionDescription.AudioCodec convert(AudioCodec codec) { + return new SdpSessionDescription.AudioCodec(codec.defaultType, + codec.name, codec.sampleRate, codec.sampleCount); + } + + private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) { + SdpSessionDescription.AudioCodec[] copies = + new SdpSessionDescription.AudioCodec[codecs.length]; + for (int i = 0, len = codecs.length; i < len; i++) { + copies[i] = convert(codecs[i]); + } + return copies; + } + + private void startCall(SdpSessionDescription peerSd) { + stopCall(DONT_RELEASE_SOCKET); + + mPeerSd = peerSd; + String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); + // TODO: handle multiple media fields + int peerMediaPort = peerSd.getPeerMediaPort(AUDIO); + Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort); + + int localPort = getLocalMediaPort(); + int sampleRate = 8000; + int frameSize = sampleRate / 50; // 160 + try { + // TODO: get sample rate from sdp + mCodec = getCodec(peerSd); + + AudioStream audioStream = mRtpSession; + audioStream.associate(InetAddress.getByName(peerMediaAddress), + peerMediaPort); + audioStream.setCodec(convert(mCodec), mCodec.payloadType); + audioStream.setDtmfType(DTMF); + Log.d(TAG, "start media: localPort=" + localPort + ", peer=" + + peerMediaAddress + ":" + peerMediaPort); + + audioStream.setMode(RtpStream.MODE_NORMAL); + if (!mHold) { + // FIXME: won't work if peer is not sending nor receiving + if (!peerSd.isSending(AUDIO)) { + Log.d(TAG, " not receiving"); + audioStream.setMode(RtpStream.MODE_SEND_ONLY); + } + if (!peerSd.isReceiving(AUDIO)) { + Log.d(TAG, " not sending"); + audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); + } + } + setInCallMode(); + + AudioGroup audioGroup = new AudioGroup(); + audioStream.join(audioGroup); + if (mHold) { + audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } else if (mMuted) { + audioGroup.setMode(AudioGroup.MODE_MUTED); + } else { + audioGroup.setMode(AudioGroup.MODE_NORMAL); + } + } catch (Exception e) { + Log.e(TAG, "call()", e); + } + } + + private void stopCall(boolean releaseSocket) { + Log.d(TAG, "stop audiocall"); + if (mRtpSession != null) { + mRtpSession.join(null); + + if (releaseSocket) { + mRtpSession.release(); + mRtpSession = null; + } + } + setInCallMode(); + } + + private int getLocalMediaPort() { + if (mRtpSession != null) return mRtpSession.getLocalPort(); + try { + AudioStream s = mRtpSession = + new AudioStream(InetAddress.getByName(getLocalIp())); + return s.getLocalPort(); + } catch (IOException e) { + Log.w(TAG, "getLocalMediaPort(): " + e); + throw new RuntimeException(e); + } + } + + private String getLocalIp() { + try { + return mSipSession.getLocalIp(); + } catch (RemoteException e) { + // FIXME + return "127.0.0.1"; + } + } + + 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/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java new file mode 100644 index 0000000..f28b41c --- /dev/null +++ b/java/android/net/sip/SipManager.java @@ -0,0 +1,499 @@ +/* + * 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.content.Intent; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; + +import java.text.ParseException; +import javax.sip.SipException; + +/** + * The class provides API for various SIP related tasks. Specifically, the API + * allows the application to: + *
    + *
  • register a {@link SipProfile} to 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(String)}, + * {@link #isOpened(String)} and {@link isRegistered(String)}. It also + * facilitates handling of the incoming call broadcast intent. See + * {@link #isIncomingCallIntent(Intent)}, {@link #getCallId(Intent)}, + * {@link #getOfferSessionDescription(Intent)} and + * {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.
  • + *
  • make/take SIP-based audio calls. See + * {@link #makeAudioCall(Context, SipProfile, SipProfile, SipAudioCall.Listener)} + * and {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener}.
  • + *
  • register/unregister with a SIP service provider. See + * {@link #register(SipProfile, int, ISipSessionListener)} and + * {@link #unregister(SipProfile, ISipSessionListener)}.
  • + *
  • process SIP events directly with a {@link ISipSession} created by + * {@link createSipSession(SipProfile, ISipSessionListener)}.
  • + *
+ * @hide + */ +public class SipManager { + /** @hide */ + public static final String SIP_INCOMING_CALL_ACTION = + "com.android.phone.SIP_INCOMING_CALL"; + /** @hide */ + public static final String SIP_ADD_PHONE_ACTION = + "com.android.phone.SIP_ADD_PHONE"; + /** @hide */ + public static final String SIP_REMOVE_PHONE_ACTION = + "com.android.phone.SIP_REMOVE_PHONE"; + /** @hide */ + public static final String LOCAL_URI_KEY = "LOCAL SIPURI"; + + private static final String CALL_ID_KEY = "CallID"; + private static final String OFFER_SD_KEY = "OfferSD"; + + private ISipService mSipService; + + // Will be removed once the SIP service is integrated into framework + private BinderHelper mBinderHelper; + + /** + * Creates a manager instance and initializes the background SIP service. + * Will be removed once the SIP service is integrated into framework. + * + * @param context context to start the SIP service + * @return the manager instance + */ + public static SipManager getInstance(final Context context) { + final SipManager manager = new SipManager(); + manager.createSipService(context); + return manager; + } + + private SipManager() { + } + + private void createSipService(Context context) { + 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(Context, String, String, SipAudioCall.Listener)}. + * 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. + * + * @param localProfile the SIP profile to make calls from + * @throws SipException if the profile contains incorrect settings or + * calling the SIP service results in an error + */ + public void open(SipProfile localProfile) throws SipException { + try { + mSipService.open(localProfile); + } catch (RemoteException e) { + throw new SipException("open()", e); + } + } + + /** + * 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(Context, String, String, SipAudioCall.Listener)}. + * 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. + * + * @param localProfile the SIP profile to receive incoming calls for + * @param incomingCallBroadcastAction the action to be broadcast when an + * incoming call is received + * @param listener to listen to registration events; can be null + * @throws SipException if the profile contains incorrect settings or + * calling the SIP service results in an error + */ + public void open(SipProfile localProfile, + String incomingCallBroadcastAction, + SipRegistrationListener listener) throws SipException { + try { + mSipService.open3(localProfile, incomingCallBroadcastAction, + createRelay(listener)); + } catch (RemoteException e) { + throw new SipException("open()", e); + } + } + + /** + * Sets the listener to listen to registration events. No effect if the + * profile has not been opened to receive calls + * (see {@link #open(SipProfile, String, SipRegistrationListener)} and + * {@link #open(SipProfile)}). + * + * @param localProfileUri the URI of the profile + * @param listener to listen to registration events; can be null + * @throws SipException if calling the SIP service results in an error + */ + public void setRegistrationListener(String localProfileUri, + SipRegistrationListener listener) throws SipException { + try { + mSipService.setRegistrationListener( + localProfileUri, createRelay(listener)); + } catch (RemoteException e) { + throw new SipException("setRegistrationListener()", e); + } + } + + /** + * Closes the specified profile to not make/receive calls. All the resources + * that were allocated to the profile are also released. + * + * @param localProfileUri the URI of the profile to close + * @throws SipException if calling the SIP service results in an error + */ + public void close(String localProfileUri) throws SipException { + try { + mSipService.close(localProfileUri); + } catch (RemoteException e) { + throw new SipException("close()", e); + } + } + + /** + * Checks if the specified profile is enabled to receive calls. + * + * @param localProfileUri the URI of the profile in question + * @return true if the profile is enabled to receive calls + * @throws SipException if calling the SIP service results in an error + */ + public boolean isOpened(String localProfileUri) throws SipException { + try { + return mSipService.isOpened(localProfileUri); + } catch (RemoteException e) { + throw new SipException("isOpened()", e); + } + } + + /** + * Checks if the specified profile is registered to the server for + * receiving calls. + * + * @param localProfileUri the URI of the profile in question + * @return true if the profile is registered to the server + * @throws SipException if calling the SIP service results in an error + */ + public boolean isRegistered(String localProfileUri) throws SipException { + try { + return mSipService.isRegistered(localProfileUri); + } catch (RemoteException e) { + throw new SipException("isRegistered()", e); + } + } + + /** + * Creates a {@link SipAudioCall} to make a call. + * + * @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}; + * can be null + * @return a {@link SipAudioCall} object + * @throws SipException if calling the SIP service results in an error + */ + public SipAudioCall makeAudioCall(Context context, SipProfile localProfile, + SipProfile peerProfile, SipAudioCall.Listener listener) + throws SipException { + SipAudioCall call = new SipAudioCallImpl(context, localProfile); + call.setListener(listener); + call.makeCall(peerProfile, this); + return call; + } + + /** + * Creates a {@link SipAudioCall} to make a call. To use this method, one + * must call {@link #open(SipProfile)} first. + * + * @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}; + * can be null + * @return a {@link SipAudioCall} object + * @throws SipException if calling the SIP service results in an error + */ + public SipAudioCall makeAudioCall(Context context, String localProfileUri, + String peerProfileUri, SipAudioCall.Listener listener) + throws SipException { + try { + return makeAudioCall(context, + new SipProfile.Builder(localProfileUri).build(), + new SipProfile.Builder(peerProfileUri).build(), listener); + } catch (ParseException e) { + throw new SipException("build SipProfile", e); + } + } + + /** + * The method calls {@code takeAudioCall(context, incomingCallIntent, + * listener, true}. + * + * @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean) + */ + public SipAudioCall takeAudioCall(Context context, + Intent incomingCallIntent, SipAudioCall.Listener listener) + throws SipException { + return takeAudioCall(context, incomingCallIntent, listener, true); + } + + /** + * Creates a {@link SipAudioCall} to take an incoming call. Before the call + * is returned, the listener will receive a + * {@link SipAudioCall#Listener.onRinging(SipAudioCall, SipProfile)} + * 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 { + if (incomingCallIntent == null) return null; + + String callId = getCallId(incomingCallIntent); + if (callId == null) { + throw new SipException("Call ID missing in incoming call intent"); + } + + byte[] offerSd = getOfferSessionDescription(incomingCallIntent); + if (offerSd == null) { + throw new SipException("Session description missing in incoming " + + "call intent"); + } + + try { + SdpSessionDescription sdp = new SdpSessionDescription(offerSd); + + ISipSession session = mSipService.getPendingSession(callId); + if (session == null) return null; + SipAudioCall call = new SipAudioCallImpl( + context, session.getLocalProfile()); + call.setRingtoneEnabled(ringtoneEnabled); + call.attachCall(session, sdp); + call.setListener(listener); + return call; + } catch (Throwable t) { + throw new SipException("takeAudioCall()", t); + } + } + + /** + * Checks if the intent is an incoming call broadcast intent. + * + * @param intent the intent in question + * @return true if the intent is an incoming call broadcast intent + */ + public static boolean isIncomingCallIntent(Intent intent) { + if (intent == null) return false; + String callId = getCallId(intent); + byte[] offerSd = getOfferSessionDescription(intent); + return ((callId != null) && (offerSd != null)); + } + + /** + * Gets the call ID from the specified incoming call broadcast intent. + * + * @param incomingCallIntent the incoming call broadcast intent + * @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); + } + + /** + * Gets the offer session description from the specified incoming call + * broadcast intent. + * + * @param incomingCallIntent the incoming call broadcast intent + * @return the offer session description or null if the intent does not + * have it + */ + public static byte[] getOfferSessionDescription(Intent incomingCallIntent) { + return incomingCallIntent.getByteArrayExtra(OFFER_SD_KEY); + } + + /** + * 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, byte[] sessionDescription) { + Intent intent = new Intent(action); + intent.putExtra(CALL_ID_KEY, callId); + intent.putExtra(OFFER_SD_KEY, sessionDescription); + return intent; + } + + /** + * Registers the profile to the corresponding server 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 second) + * @param listener to listen to the registration events + * @throws SipException if calling the SIP service results in an error + */ + public void register(SipProfile localProfile, int expiryTime, + SipRegistrationListener listener) throws SipException { + try { + ISipSession session = mSipService.createSession( + localProfile, createRelay(listener)); + session.register(expiryTime); + } catch (RemoteException e) { + throw new SipException("register()", e); + } + } + + /** + * Unregisters the profile from the corresponding server for not receiving + * further calls. + * + * @param localProfile the SIP profile to register with + * @param listener to listen to the registration events + * @throws SipException if calling the SIP service results in an error + */ + public void unregister(SipProfile localProfile, + SipRegistrationListener listener) throws SipException { + try { + ISipSession session = mSipService.createSession( + localProfile, createRelay(listener)); + session.unregister(); + } catch (RemoteException e) { + throw new SipException("unregister()", e); + } + } + + /** + * Gets the {@link ISipSession} that handles the incoming call. For audio + * calls, consider to use {@link SipAudioCall} to handle the incoming call. + * See {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}. + * Note that the method may be called only once for the same intent. For + * subsequent calls on the same intent, the method returns null. + * + * @param incomingCallIntent the incoming call broadcast intent + * @return the session object that handles the incoming call + */ + public ISipSession getSessionFor(Intent incomingCallIntent) + throws SipException { + try { + String callId = getCallId(incomingCallIntent); + return mSipService.getPendingSession(callId); + } catch (RemoteException e) { + throw new SipException("getSessionFor()", e); + } + } + + private static ISipSessionListener createRelay( + SipRegistrationListener listener) { + return ((listener == null) ? null : new ListenerRelay(listener)); + } + + /** + * Creates a {@link ISipSession} with the specified profile. Use other + * methods, if applicable, instead of interacting with {@link ISipSession} + * directly. + * + * @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 { + try { + return mSipService.createSession(localProfile, listener); + } catch (RemoteException e) { + throw new SipException("createSipSession()", e); + } + } + + /** + * Gets the list of profiles hosted by the SIP service. The user information + * (username, password and display name) are crossed out. + * @hide + */ + public SipProfile[] getListOfProfiles() { + try { + return mSipService.getListOfProfiles(); + } catch (RemoteException e) { + return null; + } + } + + private static class ListenerRelay extends SipSessionAdapter { + private SipRegistrationListener mListener; + + // listener must not be null + public ListenerRelay(SipRegistrationListener listener) { + mListener = listener; + } + + private String getUri(ISipSession session) { + try { + return session.getLocalProfile().getUriString(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + @Override + public void onRegistering(ISipSession session) { + mListener.onRegistering(getUri(session)); + } + + @Override + public void onRegistrationDone(ISipSession session, int duration) { + long expiryTime = duration; + if (duration > 0) expiryTime += System.currentTimeMillis(); + mListener.onRegistrationDone(getUri(session), expiryTime); + } + + @Override + public void onRegistrationFailed(ISipSession session, String className, + String message) { + mListener.onRegistrationFailed(getUri(session), className, message); + } + + @Override + public void onRegistrationTimeout(ISipSession session) { + mListener.onRegistrationFailed(getUri(session), + SipException.class.getName(), "registration timed out"); + } + } +} diff --git a/java/android/net/sip/SipProfile.aidl b/java/android/net/sip/SipProfile.aidl new file mode 100644 index 0000000..3b6f68f --- /dev/null +++ b/java/android/net/sip/SipProfile.aidl @@ -0,0 +1,19 @@ +/* + * 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 SipProfile; diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java new file mode 100644 index 0000000..e71c293 --- /dev/null +++ b/java/android/net/sip/SipProfile.java @@ -0,0 +1,401 @@ +/* + * 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; +import android.text.TextUtils; + +import java.io.Serializable; +import java.text.ParseException; +import javax.sip.InvalidArgumentException; +import javax.sip.ListeningPoint; +import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; +import javax.sip.address.Address; +import javax.sip.address.AddressFactory; +import javax.sip.address.SipURI; +import javax.sip.address.URI; + +/** + * Class containing a SIP account, domain and server information. + * @hide + */ +public class SipProfile implements Parcelable, Serializable { + private static final long serialVersionUID = 1L; + private static final int DEFAULT_PORT = 5060; + private Address mAddress; + private String mProxyAddress; + private String mPassword; + private String mDomain; + private String mProtocol = ListeningPoint.UDP; + private String mProfileName; + private boolean mSendKeepAlive = false; + private boolean mAutoRegistration = true; + + /** @hide */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SipProfile createFromParcel(Parcel in) { + return new SipProfile(in); + } + + public SipProfile[] newArray(int size) { + return new SipProfile[size]; + } + }; + + /** + * Class to help create a {@link SipProfile}. + */ + public static class Builder { + private AddressFactory mAddressFactory; + private SipProfile mProfile = new SipProfile(); + private SipURI mUri; + private String mDisplayName; + private String mProxyAddress; + + { + try { + mAddressFactory = + SipFactory.getInstance().createAddressFactory(); + } catch (PeerUnavailableException e) { + throw new RuntimeException(e); + } + } + + /** + * Constructor. + * + * @param uriString the URI string as "sip:@" + * @throws ParseException if the string is not a valid URI + */ + public Builder(String uriString) throws ParseException { + if (uriString == null) { + throw new NullPointerException("uriString cannot be null"); + } + URI uri = mAddressFactory.createURI(fix(uriString)); + if (uri instanceof SipURI) { + mUri = (SipURI) uri; + } else { + throw new ParseException(uriString + " is not a SIP URI", 0); + } + mProfile.mDomain = mUri.getHost(); + } + + /** + * Constructor. + * + * @param username username of the SIP account + * @param serverDomain the SIP server domain; if the network address + * is different from the domain, use + * {@link #setOutboundProxy(String)} to set server address + * @throws ParseException if the parameters are not valid + */ + public Builder(String username, String serverDomain) + throws ParseException { + if ((username == null) || (serverDomain == null)) { + throw new NullPointerException( + "username and serverDomain cannot be null"); + } + mUri = mAddressFactory.createSipURI(username, serverDomain); + mProfile.mDomain = serverDomain; + } + + private String fix(String uriString) { + return (uriString.trim().toLowerCase().startsWith("sip:") + ? uriString + : "sip:" + uriString); + } + + /** + * Sets the name of the profile. This name is given by user. + * + * @param name name of the profile + * @return this builder object + */ + public Builder setProfileName(String name) { + mProfile.mProfileName = name; + return this; + } + + /** + * Sets the password of the SIP account + * + * @param password password of the SIP account + * @return this builder object + */ + public Builder setPassword(String password) { + mUri.setUserPassword(password); + return this; + } + + /** + * Sets the port number of the server. By default, it is 5060. + * + * @param port port number of the server + * @return this builder object + * @throws InvalidArgumentException if the port number is out of range + */ + public Builder setPort(int port) throws InvalidArgumentException { + mUri.setPort(port); + return this; + } + + /** + * Sets the protocol used to connect to the SIP server. Currently, + * only "UDP" and "TCP" are supported. + * + * @param protocol the protocol string + * @return this builder object + * @throws InvalidArgumentException if the protocol is not recognized + */ + public Builder setProtocol(String protocol) + throws InvalidArgumentException { + if (protocol == null) { + throw new NullPointerException("protocol cannot be null"); + } + protocol = protocol.toUpperCase(); + if (!protocol.equals("UDP") && !protocol.equals("TCP")) { + throw new InvalidArgumentException( + "unsupported protocol: " + protocol); + } + mProfile.mProtocol = protocol; + return this; + } + + /** + * Sets the outbound proxy of the SIP server. + * + * @param outboundProxy the network address of the outbound proxy + * @return this builder object + */ + public Builder setOutboundProxy(String outboundProxy) { + mProxyAddress = outboundProxy; + return this; + } + + /** + * Sets the display name of the user. + * + * @param displayName display name of the user + * @return this builder object + */ + public Builder setDisplayName(String displayName) { + mDisplayName = displayName; + return this; + } + + /** + * Sets the send keep-alive flag. + * + * @param flag true if sending keep-alive message is required, + * false otherwise + * @return this builder object + */ + public Builder setSendKeepAlive(boolean flag) { + mProfile.mSendKeepAlive = flag; + return this; + } + + + /** + * Sets the auto. registration flag. + * + * @param flag true if the profile will be registered automatically, + * false otherwise + * @return this builder object + */ + public Builder setAutoRegistration(boolean flag) { + mProfile.mAutoRegistration = flag; + return this; + } + + /** + * Builds and returns the SIP profile object. + * + * @return the profile object created + */ + public SipProfile build() { + // remove password from URI + mProfile.mPassword = mUri.getUserPassword(); + mUri.setUserPassword(null); + try { + mProfile.mAddress = mAddressFactory.createAddress( + mDisplayName, mUri); + if (!TextUtils.isEmpty(mProxyAddress)) { + SipURI uri = (SipURI) + mAddressFactory.createURI(fix(mProxyAddress)); + mProfile.mProxyAddress = uri.getHost(); + } + } catch (ParseException e) { + // must not occur + throw new RuntimeException(e); + } + return mProfile; + } + } + + private SipProfile() { + } + + private SipProfile(Parcel in) { + mAddress = (Address) in.readSerializable(); + mProxyAddress = in.readString(); + mPassword = in.readString(); + mDomain = in.readString(); + mProtocol = in.readString(); + mProfileName = in.readString(); + mSendKeepAlive = (in.readInt() == 0) ? false : true; + mAutoRegistration = (in.readInt() == 0) ? false : true; + } + + /** @hide */ + public void writeToParcel(Parcel out, int flags) { + out.writeSerializable(mAddress); + out.writeString(mProxyAddress); + out.writeString(mPassword); + out.writeString(mDomain); + out.writeString(mProtocol); + out.writeString(mProfileName); + out.writeInt(mSendKeepAlive ? 1 : 0); + out.writeInt(mAutoRegistration ? 1 : 0); + } + + /** @hide */ + public int describeContents() { + return 0; + } + + /** + * Gets the SIP URI of this profile. + * + * @return the SIP URI of this profile + */ + public SipURI getUri() { + return (SipURI) mAddress.getURI(); + } + + /** + * Gets the SIP URI string of this profile. + * + * @return the SIP URI string of this profile + */ + public String getUriString() { + return mAddress.getURI().toString(); + } + + /** + * Gets the SIP address of this profile. + * + * @return the SIP address of this profile + */ + public Address getSipAddress() { + return mAddress; + } + + /** + * Gets the display name of the user. + * + * @return the display name of the user + */ + public String getDisplayName() { + return mAddress.getDisplayName(); + } + + /** + * Gets the username. + * + * @return the username + */ + public String getUserName() { + return getUri().getUser(); + } + + /** + * Gets the password. + * + * @return the password + */ + public String getPassword() { + return mPassword; + } + + /** + * Gets the SIP domain. + * + * @return the SIP domain + */ + public String getSipDomain() { + return mDomain; + } + + /** + * Gets the port number of the SIP server. + * + * @return the port number of the SIP server + */ + public int getPort() { + int port = getUri().getPort(); + return (port == -1) ? DEFAULT_PORT : port; + } + + /** + * Gets the protocol used to connect to the server. + * + * @return the protocol + */ + public String getProtocol() { + return mProtocol; + } + + /** + * Gets the network address of the server outbound proxy. + * + * @return the network address of the server outbound proxy + */ + public String getProxyAddress() { + return mProxyAddress; + } + + /** + * Gets the (user-defined) name of the profile. + * + * @return name of the profile + */ + public String getProfileName() { + return mProfileName; + } + + /** + * Gets the flag of 'Sending keep-alive'. + * + * @return the flag of sending SIP keep-alive messages. + */ + public boolean getSendKeepAlive() { + return mSendKeepAlive; + } + + /** + * Gets the flag of 'Auto Registration'. + * + * @return the flag of registering the profile automatically. + */ + public boolean getAutoRegistration() { + return mAutoRegistration; + } +} diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java new file mode 100644 index 0000000..63faaf8 --- /dev/null +++ b/java/android/net/sip/SipRegistrationListener.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * Listener class to listen to SIP registration events. + * @hide + */ +public interface SipRegistrationListener { + /** + * Called when a registration request is sent. + * + * @param localProfileUri the URI string of the SIP profile to register with + */ + void onRegistering(String localProfileUri); + + /** + * Called when registration is successfully done. + * + * @param localProfileUri the URI string of the SIP profile to register with + * @param expiryTime duration in second before the registration expires + */ + void onRegistrationDone(String localProfileUri, long expiryTime); + + /** + * Called when the registration fails. + * + * @param localProfileUri the URI string of the SIP profile to register with + * @param errorClass name of the exception class + * @param errorMessage error message + */ + void onRegistrationFailed(String localProfileUri, String errorClass, + String errorMessage); +} diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java new file mode 100644 index 0000000..cfb71d7 --- /dev/null +++ b/java/android/net/sip/SipSessionAdapter.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** + * Adapter class for {@link ISipSessionListener}. Default implementation of all + * callback methods is no-op. + * @hide + */ +public class SipSessionAdapter extends ISipSessionListener.Stub { + public void onCalling(ISipSession session) { + } + + public void onRinging(ISipSession session, SipProfile caller, + byte[] sessionDescription) { + } + + public void onRingingBack(ISipSession session) { + } + + public void onCallEstablished( + ISipSession session, byte[] sessionDescription) { + } + + public void onCallEnded(ISipSession session) { + } + + public void onCallBusy(ISipSession session) { + } + + public void onCallChanged(ISipSession session, byte[] sessionDescription) { + } + + public void onCallChangeFailed(ISipSession session, String className, + String message) { + } + + public void onError(ISipSession session, String className, String message) { + } + + public void onRegistering(ISipSession session) { + } + + public void onRegistrationDone(ISipSession session, int duration) { + } + + public void onRegistrationFailed(ISipSession session, String className, + String message) { + } + + public void onRegistrationTimeout(ISipSession session) { + } +} diff --git a/java/android/net/sip/SipSessionState.java b/java/android/net/sip/SipSessionState.java new file mode 100644 index 0000000..5bab112 --- /dev/null +++ b/java/android/net/sip/SipSessionState.java @@ -0,0 +1,66 @@ +/* + * 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 enum SipSessionState { + /** When session is ready to initiate a call or transaction. */ + READY_TO_CALL, + + /** When the registration request is sent out. */ + REGISTERING, + + /** When the unregistration request is sent out. */ + DEREGISTERING, + + /** When an INVITE request is received. */ + INCOMING_CALL, + + /** When an OK response is sent for the INVITE request received. */ + INCOMING_CALL_ANSWERING, + + /** When an INVITE request is sent. */ + OUTGOING_CALL, + + /** When a RINGING response is received for the INVITE request sent. */ + OUTGOING_CALL_RING_BACK, + + /** When a CANCEL request is sent for the INVITE request sent. */ + OUTGOING_CALL_CANCELING, + + /** When a call is established. */ + IN_CALL, + + /** Some error occurs when making a remote call to {@link ISipSession}. */ + REMOTE_ERROR, + + /** When an OPTIONS request is sent. */ + PINGING; + + /** + * Checks if the specified string represents the same state as this object. + * + * @return true if the specified string represents the same state as this + * object + */ + public boolean equals(String state) { + return toString().equals(state); + } +} -- cgit v1.2.3 From 320facf83d5d7fd0421b598d7dc43b837a13de1c Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 5 Aug 2010 11:09:54 +0800 Subject: Move SIP telephony related codes to framework. + hardcode the sip service for build dependency. Change-Id: Ib0e9717c9b87eb6e06ffa3a7b01ae31184de61bb --- java/android/net/sip/SipManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index f28b41c..c4e2d41 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -89,7 +89,8 @@ public class SipManager { private void createSipService(Context context) { if (mSipService != null) return; - IBinder b = ServiceManager.getService(Context.SIP_SERVICE); + // TODO: change back to Context.SIP_SERVICE later. + IBinder b = ServiceManager.getService("sip"); mSipService = ISipService.Stub.asInterface(b); } -- cgit v1.2.3 From 68144a84e3cd43ba4f62c73dbd2ce9c74d50e1a6 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 5 Aug 2010 13:25:38 +0800 Subject: Revert "Move SIP telephony related codes to framework." This reverts commit b631dcf3eb449ddec756bea330f4e70b996ffb9e. --- java/android/net/sip/SipManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index c4e2d41..f28b41c 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -89,8 +89,7 @@ public class SipManager { private void createSipService(Context context) { if (mSipService != null) return; - // TODO: change back to Context.SIP_SERVICE later. - IBinder b = ServiceManager.getService("sip"); + IBinder b = ServiceManager.getService(Context.SIP_SERVICE); mSipService = ISipService.Stub.asInterface(b); } -- cgit v1.2.3 From 73f6df1f0aac12c243e2110e30337ab01aa71598 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Fri, 6 Aug 2010 14:12:05 +0800 Subject: RTP: move into frameworks. Change-Id: Ic9c17b460448c746b21526ac10b647f281ae48e9 --- jni/rtp/Android.mk | 44 +++ jni/rtp/AudioCodec.cpp | 161 ++++++++ jni/rtp/AudioCodec.h | 36 ++ jni/rtp/AudioGroup.cpp | 1004 ++++++++++++++++++++++++++++++++++++++++++++++++ jni/rtp/RtpStream.cpp | 126 ++++++ jni/rtp/rtp_jni.cpp | 32 ++ jni/rtp/util.cpp | 61 +++ 7 files changed, 1464 insertions(+) create mode 100644 jni/rtp/Android.mk create mode 100644 jni/rtp/AudioCodec.cpp create mode 100644 jni/rtp/AudioCodec.h create mode 100644 jni/rtp/AudioGroup.cpp create mode 100644 jni/rtp/RtpStream.cpp create mode 100644 jni/rtp/rtp_jni.cpp create mode 100644 jni/rtp/util.cpp diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk new file mode 100644 index 0000000..a364355 --- /dev/null +++ b/jni/rtp/Android.mk @@ -0,0 +1,44 @@ +# +# 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := librtp_jni + +LOCAL_SRC_FILES := \ + AudioCodec.cpp \ + AudioGroup.cpp \ + RtpStream.cpp \ + util.cpp \ + rtp_jni.cpp + +LOCAL_SHARED_LIBRARIES := \ + libnativehelper \ + libcutils \ + libutils \ + libmedia + +LOCAL_STATIC_LIBRARIES := + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) + +LOCAL_CFLAGS += -fvisibility=hidden + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp new file mode 100644 index 0000000..ddd07fc --- /dev/null +++ b/jni/rtp/AudioCodec.cpp @@ -0,0 +1,161 @@ +/* + * Copyrightm (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. + */ + +#include + +#include "AudioCodec.h" + +namespace { + +int8_t gExponents[128] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +//------------------------------------------------------------------------------ + +class UlawCodec : public AudioCodec +{ +public: + bool set(int sampleRate, int sampleCount) { + mSampleCount = sampleCount; + return sampleCount > 0; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int UlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + sample += 132; + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent + 3)) & 0x0F; + ulaws[i] = ~(sign | (exponent << 4) | mantissa); + } + return mSampleCount; +} + +int UlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int ulaw = ~ulaws[i]; + int exponent = (ulaw >> 4) & 0x07; + int mantissa = ulaw & 0x0F; + int sample = (((mantissa << 3) + 132) << exponent) - 132; + samples[i] = (ulaw < 0 ? -sample : sample); + } + return length; +} + +AudioCodec *newUlawCodec() +{ + return new UlawCodec; +} + +//------------------------------------------------------------------------------ + +class AlawCodec : public AudioCodec +{ +public: + bool set(int sampleRate, int sampleCount) { + mSampleCount = sampleCount; + return sampleCount > 0; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int AlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; + alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; + } + return mSampleCount; +} + +int AlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int alaw = alaws[i] ^ 0x55; + int exponent = (alaw >> 4) & 0x07; + int mantissa = alaw & 0x0F; + int sample = (exponent == 0 ? (mantissa << 4) + 8 : + ((mantissa << 3) + 132) << exponent); + samples[i] = (alaw < 0 ? sample : -sample); + } + return length; +} + +AudioCodec *newAlawCodec() +{ + return new AlawCodec; +} + +struct AudioCodecType { + const char *name; + AudioCodec *(*create)(); +} gAudioCodecTypes[] = { + {"PCMA", newAlawCodec}, + {"PCMU", newUlawCodec}, + {NULL, NULL}, +}; + +} // namespace + +AudioCodec *newAudioCodec(const char *codecName) +{ + AudioCodecType *type = gAudioCodecTypes; + while (type->name != NULL) { + if (strcmp(codecName, type->name) == 0) { + return type->create(); + } + ++type; + } + return NULL; +} diff --git a/jni/rtp/AudioCodec.h b/jni/rtp/AudioCodec.h new file mode 100644 index 0000000..797494c --- /dev/null +++ b/jni/rtp/AudioCodec.h @@ -0,0 +1,36 @@ +/* + * Copyrightm (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. + */ + +#include + +#ifndef __AUDIO_CODEC_H__ +#define __AUDIO_CODEC_H__ + +class AudioCodec +{ +public: + virtual ~AudioCodec() {} + // Returns true if initialization succeeds. + virtual bool set(int sampleRate, int sampleCount) = 0; + // Returns the length of payload in bytes. + virtual int encode(void *payload, int16_t *samples) = 0; + // Returns the number of decoded samples. + virtual int decode(int16_t *samples, void *payload, int length) = 0; +}; + +AudioCodec *newAudioCodec(const char *codecName); + +#endif diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp new file mode 100644 index 0000000..fc1ed9b --- /dev/null +++ b/jni/rtp/AudioGroup.cpp @@ -0,0 +1,1004 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "AudioGroup" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jni.h" +#include "JNIHelp.h" + +#include "AudioCodec.h" + +extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); + +namespace { + +using namespace android; + +int gRandom = -1; + +// We use a circular array to implement jitter buffer. The simplest way is doing +// a modulo operation on the index while accessing the array. However modulo can +// be expensive on some platforms, such as ARM. Thus we round up the size of the +// array to the nearest power of 2 and then use bitwise-and instead of modulo. +// Currently we make it 256ms long and assume packet interval is 32ms or less. +// The first 64ms is the place where samples get mixed. The rest 192ms is the +// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers +// are chosen by experiments and each of them can be adjusted as needed. + +// Other notes: +// + We use elapsedRealtime() to get the time. Since we use 32bit variables +// instead of 64bit ones, comparison must be done by subtraction. +// + Sampling rate must be multiple of 1000Hz, and packet length must be in +// milliseconds. No floating points. +// + If we cannot get enough CPU, we drop samples and simulate packet loss. +// + Resampling is not done yet, so streams in one group must use the same rate. +// For the first release we might only support 8kHz and 16kHz. + +class AudioStream +{ +public: + AudioStream(); + ~AudioStream(); + bool set(int mode, int socket, sockaddr_storage *remote, + const char *codecName, int sampleRate, int sampleCount, + int codecType, int dtmfType); + + void sendDtmf(int event); + bool mix(int32_t *output, int head, int tail, int sampleRate); + void encode(int tick, AudioStream *chain); + void decode(int tick); + +private: + enum { + NORMAL = 0, + SEND_ONLY = 1, + RECEIVE_ONLY = 2, + LAST_MODE = 2, + }; + + int mMode; + int mSocket; + sockaddr_storage mRemote; + AudioCodec *mCodec; + uint32_t mCodecMagic; + uint32_t mDtmfMagic; + + int mTick; + int mSampleRate; + int mSampleCount; + int mInterval; + + int16_t *mBuffer; + int mBufferMask; + int mBufferHead; + int mBufferTail; + int mLatencyScore; + + uint16_t mSequence; + uint32_t mTimestamp; + uint32_t mSsrc; + + int mDtmfEvent; + int mDtmfStart; + + AudioStream *mNext; + + friend class AudioGroup; +}; + +AudioStream::AudioStream() +{ + mSocket = -1; + mCodec = NULL; + mBuffer = NULL; + mNext = NULL; +} + +AudioStream::~AudioStream() +{ + close(mSocket); + delete mCodec; + delete [] mBuffer; + LOGD("stream[%d] is dead", mSocket); +} + +bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, + const char *codecName, int sampleRate, int sampleCount, + int codecType, int dtmfType) +{ + if (mode < 0 || mode > LAST_MODE) { + return false; + } + mMode = mode; + + if (codecName) { + mRemote = *remote; + mCodec = newAudioCodec(codecName); + if (!mCodec || !mCodec->set(sampleRate, sampleCount)) { + return false; + } + } + + mCodecMagic = (0x8000 | codecType) << 16; + mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16; + + mTick = elapsedRealtime(); + mSampleRate = sampleRate / 1000; + mSampleCount = sampleCount; + mInterval = mSampleCount / mSampleRate; + + // Allocate jitter buffer. + for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1); + mBufferMask >>= 2; + mBuffer = new int16_t[mBufferMask]; + --mBufferMask; + mBufferHead = 0; + mBufferTail = 0; + mLatencyScore = 0; + + // Initialize random bits. + read(gRandom, &mSequence, sizeof(mSequence)); + read(gRandom, &mTimestamp, sizeof(mTimestamp)); + read(gRandom, &mSsrc, sizeof(mSsrc)); + + mDtmfEvent = -1; + mDtmfStart = 0; + + // Only take over the socket when succeeded. + mSocket = socket; + + LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, + (codecName ? codecName : "RAW"), mSampleRate, mInterval); + return true; +} + +void AudioStream::sendDtmf(int event) +{ + if (mDtmfMagic != 0) { + mDtmfEvent = event << 24; + mDtmfStart = mTimestamp + mSampleCount; + } +} + +bool AudioStream::mix(int32_t *output, int head, int tail, int sampleRate) +{ + if (mMode == SEND_ONLY) { + return false; + } + + if (head - mBufferHead < 0) { + head = mBufferHead; + } + if (tail - mBufferTail > 0) { + tail = mBufferTail; + } + if (tail - head <= 0) { + return false; + } + + head *= mSampleRate; + tail *= mSampleRate; + + if (sampleRate == mSampleRate) { + for (int i = head; i - tail < 0; ++i) { + output[i - head] += mBuffer[i & mBufferMask]; + } + } else { + // TODO: implement resampling. + return false; + } + return true; +} + +void AudioStream::encode(int tick, AudioStream *chain) +{ + if (tick - mTick >= mInterval) { + // We just missed the train. Pretend that packets in between are lost. + int skipped = (tick - mTick) / mInterval; + mTick += skipped * mInterval; + mSequence += skipped; + mTimestamp += skipped * mSampleCount; + LOGD("stream[%d] skips %d packets", mSocket, skipped); + } + + tick = mTick; + mTick += mInterval; + ++mSequence; + mTimestamp += mSampleCount; + + if (mMode == RECEIVE_ONLY) { + return; + } + + // If there is an ongoing DTMF event, send it now. + if (mDtmfEvent != -1) { + int duration = mTimestamp - mDtmfStart; + // Make sure duration is reasonable. + if (duration >= 0 && duration < mSampleRate * 100) { + duration += mSampleCount; + int32_t buffer[4] = { + htonl(mDtmfMagic | mSequence), + htonl(mDtmfStart), + mSsrc, + htonl(mDtmfEvent | duration), + }; + if (duration >= mSampleRate * 100) { + buffer[3] |= htonl(1 << 23); + mDtmfEvent = -1; + } + sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT, + (sockaddr *)&mRemote, sizeof(mRemote)); + return; + } + mDtmfEvent = -1; + } + + // It is time to mix streams. + bool mixed = false; + int32_t buffer[mSampleCount + 3]; + memset(buffer, 0, sizeof(buffer)); + while (chain) { + if (chain != this && + chain->mix(buffer, tick - mInterval, tick, mSampleRate)) { + mixed = true; + } + chain = chain->mNext; + } + if (!mixed) { + LOGD("stream[%d] no data", mSocket); + return; + } + + // Cook the packet and send it out. + int16_t samples[mSampleCount]; + for (int i = 0; i < mSampleCount; ++i) { + int32_t sample = buffer[i]; + if (sample < -32768) { + sample = -32768; + } + if (sample > 32767) { + sample = 32767; + } + samples[i] = sample; + } + if (!mCodec) { + // Special case for device stream. + send(mSocket, samples, sizeof(samples), MSG_DONTWAIT); + return; + } + + buffer[0] = htonl(mCodecMagic | mSequence); + buffer[1] = htonl(mTimestamp); + buffer[2] = mSsrc; + int length = mCodec->encode(&buffer[3], samples); + if (length <= 0) { + LOGD("stream[%d] encoder error", mSocket); + return; + } + sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, + sizeof(mRemote)); +} + +void AudioStream::decode(int tick) +{ + char c; + if (mMode == SEND_ONLY) { + recv(mSocket, &c, 1, MSG_DONTWAIT); + return; + } + + // Make sure mBufferHead and mBufferTail are reasonable. + if ((unsigned int)(tick + 256 - mBufferHead) > 1024) { + mBufferHead = tick - 64; + mBufferTail = mBufferHead; + } + + if (tick - mBufferHead > 64) { + // Throw away outdated samples. + mBufferHead = tick - 64; + if (mBufferTail - mBufferHead < 0) { + mBufferTail = mBufferHead; + } + } + + if (mBufferTail - tick <= 80) { + mLatencyScore = tick; + } else if (tick - mLatencyScore >= 5000) { + // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms + // in the past 5s. This rarely happens, so let us just keep it simple. + LOGD("stream[%d] latency control", mSocket); + mBufferTail = tick + 40; + } + + if (mBufferTail - mBufferHead > 256 - mInterval) { + // Buffer overflow. Drop the packet. + LOGD("stream[%d] buffer overflow", mSocket); + recv(mSocket, &c, 1, MSG_DONTWAIT); + return; + } + + // Receive the packet and decode it. + int16_t samples[mSampleCount]; + int length = 0; + if (!mCodec) { + // Special case for device stream. + length = recv(mSocket, samples, sizeof(samples), + MSG_TRUNC | MSG_DONTWAIT) >> 1; + } else { + __attribute__((aligned(4))) uint8_t buffer[2048]; + length = recv(mSocket, buffer, sizeof(buffer), + MSG_TRUNC | MSG_DONTWAIT); + + // Do we need to check SSRC, sequence, and timestamp? They are not + // reliable but at least they can be used to identity duplicates? + if (length < 12 || length > (int)sizeof(buffer) || + (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { + LOGD("stream[%d] malformed packet", mSocket); + return; + } + int offset = 12 + ((buffer[0] & 0x0F) << 2); + if ((buffer[0] & 0x10) != 0) { + offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2); + } + if ((buffer[0] & 0x20) != 0) { + length -= buffer[length - 1]; + } + length -= offset; + if (length >= 0) { + length = mCodec->decode(samples, &buffer[offset], length); + } + } + if (length != mSampleCount) { + LOGD("stream[%d] decoder error", mSocket); + return; + } + + if (tick - mBufferTail > 0) { + // Buffer underrun. Reset the jitter buffer to 40ms. + LOGD("stream[%d] buffer underrun", mSocket); + if (mBufferTail - mBufferHead <= 0) { + mBufferHead = tick + 40; + mBufferTail = mBufferHead; + } else { + int tail = (tick + 40) * mSampleRate; + for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) { + mBuffer[i & mBufferMask] = 0; + } + mBufferTail = tick + 40; + } + } + + // Append to the jitter buffer. + int tail = mBufferTail * mSampleRate; + for (int i = 0; i < mSampleCount; ++i) { + mBuffer[tail & mBufferMask] = samples[i]; + ++tail; + } + mBufferTail += mInterval; +} + +//------------------------------------------------------------------------------ + +class AudioGroup +{ +public: + AudioGroup(); + ~AudioGroup(); + bool set(int sampleRate, int sampleCount); + + bool setMode(int mode); + bool sendDtmf(int event); + bool add(AudioStream *stream); + bool remove(int socket); + +private: + enum { + ON_HOLD = 0, + MUTED = 1, + NORMAL = 2, + EC_ENABLED = 3, + LAST_MODE = 3, + }; + int mMode; + AudioStream *mChain; + int mEventQueue; + volatile int mDtmfEvent; + + int mSampleCount; + int mDeviceSocket; + AudioTrack mTrack; + AudioRecord mRecord; + + bool networkLoop(); + bool deviceLoop(); + + class NetworkThread : public Thread + { + public: + NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {} + + bool start() + { + if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { + LOGE("cannot start network thread"); + return false; + } + return true; + } + + private: + AudioGroup *mGroup; + bool threadLoop() + { + return mGroup->networkLoop(); + } + }; + sp mNetworkThread; + + class DeviceThread : public Thread + { + public: + DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {} + + bool start() + { + char c; + while (recv(mGroup->mDeviceSocket, &c, 1, MSG_DONTWAIT) == 1); + + if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { + LOGE("cannot start device thread"); + return false; + } + return true; + } + + private: + AudioGroup *mGroup; + bool threadLoop() + { + return mGroup->deviceLoop(); + } + }; + sp mDeviceThread; +}; + +AudioGroup::AudioGroup() +{ + mMode = ON_HOLD; + mChain = NULL; + mEventQueue = -1; + mDtmfEvent = -1; + mDeviceSocket = -1; + mNetworkThread = new NetworkThread(this); + mDeviceThread = new DeviceThread(this); +} + +AudioGroup::~AudioGroup() +{ + mNetworkThread->requestExitAndWait(); + mDeviceThread->requestExitAndWait(); + mTrack.stop(); + mRecord.stop(); + close(mEventQueue); + close(mDeviceSocket); + while (mChain) { + AudioStream *next = mChain->mNext; + delete mChain; + mChain = next; + } + LOGD("group[%d] is dead", mDeviceSocket); +} + +#define FROYO_COMPATIBLE +#ifdef FROYO_COMPATIBLE + +// Copied from AudioRecord.cpp. +status_t AudioRecord_getMinFrameCount( + int* frameCount, + uint32_t sampleRate, + int format, + int channelCount) +{ + size_t size = 0; + if (AudioSystem::getInputBufferSize(sampleRate, format, channelCount, &size) + != NO_ERROR) { + LOGE("AudioSystem could not query the input buffer size."); + return NO_INIT; + } + + if (size == 0) { + LOGE("Unsupported configuration: sampleRate %d, format %d, channelCount %d", + sampleRate, format, channelCount); + return BAD_VALUE; + } + + // We double the size of input buffer for ping pong use of record buffer. + size <<= 1; + + if (AudioSystem::isLinearPCM(format)) { + size /= channelCount * (format == AudioSystem::PCM_16_BIT ? 2 : 1); + } + + *frameCount = size; + return NO_ERROR; +} + +// Copied from AudioTrack.cpp. +status_t AudioTrack_getMinFrameCount( + int* frameCount, + int streamType, + uint32_t sampleRate) +{ + int afSampleRate; + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + return NO_INIT; + } + int afFrameCount; + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + return NO_INIT; + } + uint32_t afLatency; + if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { + return NO_INIT; + } + + // Ensure that buffer depth covers at least audio hardware latency + uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate); + if (minBufCount < 2) minBufCount = 2; + + *frameCount = (sampleRate == 0) ? afFrameCount * minBufCount : + afFrameCount * minBufCount * sampleRate / afSampleRate; + return NO_ERROR; +} + +#endif + +bool AudioGroup::set(int sampleRate, int sampleCount) +{ + mEventQueue = epoll_create(2); + if (mEventQueue == -1) { + LOGE("epoll_create: %s", strerror(errno)); + return false; + } + + mSampleCount = sampleCount; + + // Find out the frame count for AudioTrack and AudioRecord. + int output = 0; + int input = 0; +#ifdef FROYO_COMPATIBLE + if (AudioTrack_getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord_getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; + } +#else + if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord::getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; + } +#endif + LOGD("reported frame count: output %d, input %d", output, input); + + output = (output + sampleCount - 1) / sampleCount * sampleCount; + input = (input + sampleCount - 1) / sampleCount * sampleCount; + if (input < output * 2) { + input = output * 2; + } + LOGD("adjusted frame count: output %d, input %d", output, input); + + // Initialize AudioTrack and AudioRecord. + if (mTrack.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || + mRecord.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { + LOGE("cannot initialize audio device"); + return false; + } + LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); + + // TODO: initialize echo canceler here. + + // Create device socket. + int pair[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { + LOGE("socketpair: %s", strerror(errno)); + return false; + } + mDeviceSocket = pair[0]; + + // Create device stream. + mChain = new AudioStream; + if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL, + sampleRate, sampleCount, -1, -1)) { + close(pair[1]); + LOGE("cannot initialize device stream"); + return false; + } + + // Give device socket a reasonable timeout and buffer size. + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000 * sampleCount / sampleRate * 500; + if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || + setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || + setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { + LOGE("setsockopt: %s", strerror(errno)); + return false; + } + + // Add device stream into event queue. + epoll_event event; + event.events = EPOLLIN; + event.data.ptr = mChain; + if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) { + LOGE("epoll_ctl: %s", strerror(errno)); + return false; + } + + // Anything else? + LOGD("stream[%d] joins group[%d]", pair[1], pair[0]); + return true; +} + +bool AudioGroup::setMode(int mode) +{ + if (mode < 0 || mode > LAST_MODE) { + return false; + } + if (mMode == mode) { + return true; + } + + LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); + mMode = mode; + + mDeviceThread->requestExitAndWait(); + if (mode == ON_HOLD) { + mTrack.stop(); + mRecord.stop(); + return true; + } + + mTrack.start(); + if (mode == MUTED) { + mRecord.stop(); + } else { + mRecord.start(); + } + + if (!mDeviceThread->start()) { + mTrack.stop(); + mRecord.stop(); + return false; + } + return true; +} + +bool AudioGroup::sendDtmf(int event) +{ + if (event < 0 || event > 15) { + return false; + } + + // DTMF is rarely used, so we try to make it as lightweight as possible. + // Using volatile might be dodgy, but using a pipe or pthread primitives + // or stop-set-restart threads seems too heavy. Will investigate later. + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100000000; + for (int i = 0; mDtmfEvent != -1 && i < 20; ++i) { + nanosleep(&ts, NULL); + } + if (mDtmfEvent != -1) { + return false; + } + mDtmfEvent = event; + nanosleep(&ts, NULL); + return true; +} + +bool AudioGroup::add(AudioStream *stream) +{ + mNetworkThread->requestExitAndWait(); + + epoll_event event; + event.events = EPOLLIN; + event.data.ptr = stream; + if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) { + LOGE("epoll_ctl: %s", strerror(errno)); + return false; + } + + stream->mNext = mChain->mNext; + mChain->mNext = stream; + if (!mNetworkThread->start()) { + // Only take over the stream when succeeded. + mChain->mNext = stream->mNext; + return false; + } + + LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); + return true; +} + +bool AudioGroup::remove(int socket) +{ + mNetworkThread->requestExitAndWait(); + + for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) { + AudioStream *target = stream->mNext; + if (target->mSocket == socket) { + stream->mNext = target->mNext; + LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); + delete target; + break; + } + } + + // Do not start network thread if there is only one stream. + if (!mChain->mNext || !mNetworkThread->start()) { + return false; + } + return true; +} + +bool AudioGroup::networkLoop() +{ + int tick = elapsedRealtime(); + int deadline = tick + 10; + int count = 0; + + for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + if (!stream->mTick || tick - stream->mTick >= 0) { + stream->encode(tick, mChain); + } + if (deadline - stream->mTick > 0) { + deadline = stream->mTick; + } + ++count; + } + + if (mDtmfEvent != -1) { + int event = mDtmfEvent; + for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + stream->sendDtmf(event); + } + mDtmfEvent = -1; + } + + deadline -= tick; + if (deadline < 1) { + deadline = 1; + } + + epoll_event events[count]; + count = epoll_wait(mEventQueue, events, count, deadline); + if (count == -1) { + LOGE("epoll_wait: %s", strerror(errno)); + return false; + } + for (int i = 0; i < count; ++i) { + ((AudioStream *)events[i].data.ptr)->decode(tick); + } + + return true; +} + +bool AudioGroup::deviceLoop() +{ + int16_t output[mSampleCount]; + + if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) { + memset(output, 0, sizeof(output)); + } + if (mTrack.write(output, sizeof(output)) != (int)sizeof(output)) { + LOGE("cannot write to AudioTrack"); + return false; + } + + if (mMode != MUTED) { + uint32_t frameCount = mRecord.frameCount(); + AudioRecord::Buffer input; + input.frameCount = frameCount; + + if (mRecord.obtainBuffer(&input, -1) != NO_ERROR) { + LOGE("cannot read from AudioRecord"); + return false; + } + + if (input.frameCount < (uint32_t)mSampleCount) { + input.frameCount = 0; + } else { + if (mMode == NORMAL) { + send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); + } else { + // TODO: Echo canceller runs here. + send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); + } + if (input.frameCount < frameCount) { + input.frameCount = mSampleCount; + } + } + + mRecord.releaseBuffer(&input); + } + + return true; +} + +//------------------------------------------------------------------------------ + +static jfieldID gNative; +static jfieldID gMode; + +jint add(JNIEnv *env, jobject thiz, jint mode, + jint socket, jstring jRemoteAddress, jint remotePort, + jstring jCodecName, jint sampleRate, jint sampleCount, + jint codecType, jint dtmfType) +{ + const char *codecName = NULL; + AudioStream *stream = NULL; + AudioGroup *group = NULL; + + // Sanity check. + sockaddr_storage remote; + if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { + // Exception already thrown. + return -1; + } + if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + goto error; + } + if (!jCodecName) { + jniThrowNullPointerException(env, "codecName"); + return -1; + } + codecName = env->GetStringUTFChars(jCodecName, NULL); + if (!codecName) { + // Exception already thrown. + return -1; + } + + // Create audio stream. + stream = new AudioStream; + if (!stream->set(mode, socket, &remote, codecName, sampleRate, sampleCount, + codecType, dtmfType)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot initialize audio stream"); + goto error; + } + socket = -1; + + // Create audio group. + group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (!group) { + int mode = env->GetIntField(thiz, gMode); + group = new AudioGroup; + if (!group->set(8000, 256) || !group->setMode(mode)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot initialize audio group"); + goto error; + } + } + + // Add audio stream into audio group. + if (!group->add(stream)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot add audio stream"); + goto error; + } + + // Succeed. + env->SetIntField(thiz, gNative, (int)group); + env->ReleaseStringUTFChars(jCodecName, codecName); + return socket; + +error: + delete group; + delete stream; + close(socket); + env->SetIntField(thiz, gNative, NULL); + env->ReleaseStringUTFChars(jCodecName, codecName); + return -1; +} + +void remove(JNIEnv *env, jobject thiz, jint socket) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group) { + if (socket == -1 || !group->remove(socket)) { + delete group; + env->SetIntField(thiz, gNative, NULL); + } + } +} + +void setMode(JNIEnv *env, jobject thiz, jint mode) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group && !group->setMode(mode)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + env->SetIntField(thiz, gMode, mode); +} + +void sendDtmf(JNIEnv *env, jobject thiz, jint event) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group && !group->sendDtmf(event)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + } +} + +JNINativeMethod gMethods[] = { + {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)I", (void *)add}, + {"remove", "(I)V", (void *)remove}, + {"setMode", "(I)V", (void *)setMode}, + {"sendDtmf", "(I)V", (void *)sendDtmf}, +}; + +} // namespace + +int registerAudioGroup(JNIEnv *env) +{ + gRandom = open("/dev/urandom", O_RDONLY); + if (gRandom == -1) { + LOGE("urandom: %s", strerror(errno)); + return -1; + } + + jclass clazz; + if ((clazz = env->FindClass("android/net/rtp/AudioGroup")) == NULL || + (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL || + env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { + LOGE("JNI registration failed"); + return -1; + } + return 0; +} diff --git a/jni/rtp/RtpStream.cpp b/jni/rtp/RtpStream.cpp new file mode 100644 index 0000000..33b88e4 --- /dev/null +++ b/jni/rtp/RtpStream.cpp @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "RtpStream" +#include + +#include "jni.h" +#include "JNIHelp.h" + +extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); + +namespace { + +jfieldID gNative; + +jint create(JNIEnv *env, jobject thiz, jstring jAddress) +{ + env->SetIntField(thiz, gNative, -1); + + sockaddr_storage ss; + if (parse(env, jAddress, 0, &ss) < 0) { + // Exception already thrown. + return -1; + } + + int socket = ::socket(ss.ss_family, SOCK_DGRAM, 0); + socklen_t len = sizeof(ss); + if (socket == -1 || bind(socket, (sockaddr *)&ss, sizeof(ss)) != 0 || + getsockname(socket, (sockaddr *)&ss, &len) != 0) { + jniThrowException(env, "java/net/SocketException", strerror(errno)); + ::close(socket); + return -1; + } + + uint16_t *p = (ss.ss_family == AF_INET) ? + &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port; + uint16_t port = ntohs(*p); + if ((port & 1) == 0) { + env->SetIntField(thiz, gNative, socket); + return port; + } + ::close(socket); + + socket = ::socket(ss.ss_family, SOCK_DGRAM, 0); + if (socket != -1) { + uint16_t delta = port << 1; + ++port; + + for (int i = 0; i < 1000; ++i) { + do { + port += delta; + } while (port < 1024); + *p = htons(port); + + if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) { + env->SetIntField(thiz, gNative, socket); + return port; + } + } + } + + jniThrowException(env, "java/net/SocketException", strerror(errno)); + ::close(socket); + return -1; +} + +jint dup(JNIEnv *env, jobject thiz) +{ + int socket1 = env->GetIntField(thiz, gNative); + int socket2 = ::dup(socket1); + if (socket2 == -1) { + jniThrowException(env, "java/lang/IllegalStateException", strerror(errno)); + } + LOGD("dup %d to %d", socket1, socket2); + return socket2; +} + +void close(JNIEnv *env, jobject thiz) +{ + int socket = env->GetIntField(thiz, gNative); + ::close(socket); + env->SetIntField(thiz, gNative, -1); + LOGD("close %d", socket); +} + +JNINativeMethod gMethods[] = { + {"create", "(Ljava/lang/String;)I", (void *)create}, + {"dup", "()I", (void *)dup}, + {"close", "()V", (void *)close}, +}; + +} // namespace + +int registerRtpStream(JNIEnv *env) +{ + jclass clazz; + if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || + (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { + LOGE("JNI registration failed"); + return -1; + } + return 0; +} diff --git a/jni/rtp/rtp_jni.cpp b/jni/rtp/rtp_jni.cpp new file mode 100644 index 0000000..9f4bff9 --- /dev/null +++ b/jni/rtp/rtp_jni.cpp @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#include + +#include "jni.h" + +extern int registerRtpStream(JNIEnv *env); +extern int registerAudioGroup(JNIEnv *env); + +__attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM *vm, void *unused) +{ + JNIEnv *env = NULL; + if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK || + registerRtpStream(env) < 0 || registerAudioGroup(env) < 0) { + return -1; + } + return JNI_VERSION_1_4; +} diff --git a/jni/rtp/util.cpp b/jni/rtp/util.cpp new file mode 100644 index 0000000..1d702fc --- /dev/null +++ b/jni/rtp/util.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "jni.h" +#include "JNIHelp.h" + +int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss) +{ + if (!jAddress) { + jniThrowNullPointerException(env, "address"); + return -1; + } + if (port < 0 || port > 65535) { + jniThrowException(env, "java/lang/IllegalArgumentException", "port"); + return -1; + } + const char *address = env->GetStringUTFChars(jAddress, NULL); + if (!address) { + // Exception already thrown. + return -1; + } + memset(ss, 0, sizeof(*ss)); + + sockaddr_in *sin = (sockaddr_in *)ss; + if (inet_pton(AF_INET, address, &(sin->sin_addr)) > 0) { + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + env->ReleaseStringUTFChars(jAddress, address); + return 0; + } + + sockaddr_in6 *sin6 = (sockaddr_in6 *)ss; + if (inet_pton(AF_INET6, address, &(sin6->sin6_addr)) > 0) { + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + env->ReleaseStringUTFChars(jAddress, address); + return 0; + } + + env->ReleaseStringUTFChars(jAddress, address); + jniThrowException(env, "java/lang/IllegalArgumentException", "address"); + return -1; +} -- cgit v1.2.3 From 163e78bc69089ab5d59ed563ef34af16c55f571a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 10 Aug 2010 12:00:45 +0800 Subject: SIP: clean up unused class and fields. Change-Id: I79ed7fb324fea9a52946340055b5ea1d389a926a --- java/android/net/sip/BinderHelper.java | 79 ---------------------------------- java/android/net/sip/SipManager.java | 3 -- 2 files changed, 82 deletions(-) delete mode 100644 java/android/net/sip/BinderHelper.java diff --git a/java/android/net/sip/BinderHelper.java b/java/android/net/sip/BinderHelper.java deleted file mode 100644 index bd3da32..0000000 --- a/java/android/net/sip/BinderHelper.java +++ /dev/null @@ -1,79 +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.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.ConditionVariable; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Looper; -import android.util.Log; - -// TODO: throw away this class after moving SIP classes to framework -// This class helps to get IBinder instance of a service in a blocking call. -// The method cannot be called in app's main thread as the ServiceConnection -// callback will. -class BinderHelper { - private Context mContext; - private IBinder mBinder; - private Class mClass; - - BinderHelper(Context context, Class klass) { - mContext = context; - mClass = klass; - } - - void startService() { - mContext.startService(new Intent(mClass.getName())); - } - - void stopService() { - mContext.stopService(new Intent(mClass.getName())); - } - - IBinder getBinder() { - // cannot call this method in app's main thread - if (Looper.getMainLooper().getThread() == Thread.currentThread()) { - throw new RuntimeException( - "This method cannot be called in app's main thread"); - } - - final ConditionVariable cv = new ConditionVariable(); - cv.close(); - ServiceConnection c = new ServiceConnection() { - public synchronized void onServiceConnected( - ComponentName className, IBinder binder) { - Log.v("BinderHelper", "service connected!"); - mBinder = binder; - cv.open(); - mContext.unbindService(this); - } - - public void onServiceDisconnected(ComponentName className) { - cv.open(); - mContext.unbindService(this); - } - }; - if (mContext.bindService(new Intent(mClass.getName()), c, 0)) { - cv.block(4500); - } - return mBinder; - } -} diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index f28b41c..287a13a 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -68,9 +68,6 @@ public class SipManager { private ISipService mSipService; - // Will be removed once the SIP service is integrated into framework - private BinderHelper mBinderHelper; - /** * Creates a manager instance and initializes the background SIP service. * Will be removed once the SIP service is integrated into framework. -- cgit v1.2.3 From 92a48830c7e1468155db24aa604efbbbbef8afc7 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 10 Aug 2010 13:23:12 +0800 Subject: SipAudioCall: perform local ops before network op in endCall() Change-Id: I1808f715d56c0979cea7741cb5bdb3831774d3ef --- java/android/net/sip/SipAudioCallImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 57e0bd2..474bc4b 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -343,8 +343,11 @@ public class SipAudioCallImpl extends SipSessionAdapter public synchronized void endCall() throws SipException { try { stopRinging(); - if (mSipSession != null) mSipSession.endCall(); stopCall(true); + mInCall = false; + + // perform the above local ops first and then network op + if (mSipSession != null) mSipSession.endCall(); } catch (Throwable e) { throwSipException(e); } -- cgit v1.2.3 From b8298544a5e3f292fed1fef8cbb578789f3577cc Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Mon, 16 Aug 2010 17:44:36 +0800 Subject: Fix the IN_CALL mode issue. If the sip call is on-holding, we should not set the audio to MODE_NORMAL, or it will affect the audio if there is an active pstn call. Change-Id: If1bcba952617bf8427bc9e2d64d483ba1ee37370 --- java/android/net/sip/SipAudioCall.java | 5 +---- java/android/net/sip/SipAudioCallImpl.java | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index abdc9d7..f4be839 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -191,11 +191,8 @@ public interface SipAudioCall { */ void continueCall() throws SipException; - /** Puts the device to in-call mode. */ - void setInCallMode(); - /** Puts the device to speaker mode. */ - void setSpeakerMode(); + void setSpeakerMode(boolean speakerMode); /** Toggles mute. */ void toggleMute(); diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 474bc4b..7161309 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -485,16 +485,9 @@ public class SipAudioCallImpl extends SipSessionAdapter return mMuted; } - public synchronized void setInCallMode() { + public synchronized void setSpeakerMode(boolean speakerMode) { ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(false); - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setMode(AudioManager.MODE_NORMAL); - } - - public synchronized void setSpeakerMode() { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(true); + .setSpeakerphoneOn(speakerMode); } public void sendDtmf(int code) { @@ -587,8 +580,15 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.d(TAG, " not sending"); audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); } + } else { + /* 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); } - setInCallMode(); AudioGroup audioGroup = new AudioGroup(); audioStream.join(audioGroup); @@ -614,7 +614,6 @@ public class SipAudioCallImpl extends SipSessionAdapter mRtpSession = null; } } - setInCallMode(); } private int getLocalMediaPort() { -- cgit v1.2.3 From 2ea02d959b8c3e543e12965ecaa507b1528af278 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 18 Aug 2010 07:09:59 +0800 Subject: RTP: remove froyo-compatible code. Change-Id: I6822a4e4749a5909959658c29253242b4018aeb0 --- jni/rtp/AudioGroup.cpp | 74 -------------------------------------------------- 1 file changed, 74 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index fc1ed9b..63a60ea 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -526,70 +526,6 @@ AudioGroup::~AudioGroup() LOGD("group[%d] is dead", mDeviceSocket); } -#define FROYO_COMPATIBLE -#ifdef FROYO_COMPATIBLE - -// Copied from AudioRecord.cpp. -status_t AudioRecord_getMinFrameCount( - int* frameCount, - uint32_t sampleRate, - int format, - int channelCount) -{ - size_t size = 0; - if (AudioSystem::getInputBufferSize(sampleRate, format, channelCount, &size) - != NO_ERROR) { - LOGE("AudioSystem could not query the input buffer size."); - return NO_INIT; - } - - if (size == 0) { - LOGE("Unsupported configuration: sampleRate %d, format %d, channelCount %d", - sampleRate, format, channelCount); - return BAD_VALUE; - } - - // We double the size of input buffer for ping pong use of record buffer. - size <<= 1; - - if (AudioSystem::isLinearPCM(format)) { - size /= channelCount * (format == AudioSystem::PCM_16_BIT ? 2 : 1); - } - - *frameCount = size; - return NO_ERROR; -} - -// Copied from AudioTrack.cpp. -status_t AudioTrack_getMinFrameCount( - int* frameCount, - int streamType, - uint32_t sampleRate) -{ - int afSampleRate; - if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { - return NO_INIT; - } - int afFrameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { - return NO_INIT; - } - uint32_t afLatency; - if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { - return NO_INIT; - } - - // Ensure that buffer depth covers at least audio hardware latency - uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate); - if (minBufCount < 2) minBufCount = 2; - - *frameCount = (sampleRate == 0) ? afFrameCount * minBufCount : - afFrameCount * minBufCount * sampleRate / afSampleRate; - return NO_ERROR; -} - -#endif - bool AudioGroup::set(int sampleRate, int sampleCount) { mEventQueue = epoll_create(2); @@ -603,15 +539,6 @@ bool AudioGroup::set(int sampleRate, int sampleCount) // Find out the frame count for AudioTrack and AudioRecord. int output = 0; int input = 0; -#ifdef FROYO_COMPATIBLE - if (AudioTrack_getMinFrameCount(&output, AudioSystem::VOICE_CALL, - sampleRate) != NO_ERROR || output <= 0 || - AudioRecord_getMinFrameCount(&input, sampleRate, - AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { - LOGE("cannot compute frame count"); - return false; - } -#else if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, @@ -619,7 +546,6 @@ bool AudioGroup::set(int sampleRate, int sampleCount) LOGE("cannot compute frame count"); return false; } -#endif LOGD("reported frame count: output %d, input %d", output, input); output = (output + sampleCount - 1) / sampleCount * sampleCount; -- cgit v1.2.3 From 19f94d0256b9d50fe2656dc5c27ee56a24f92247 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 19 Aug 2010 18:26:53 +0800 Subject: RTP: fix few leaks when fail to add streams into a group. Change-Id: Iefb3fe219ad48641da37a83c8d14e9ebf1d3086c --- java/android/net/rtp/AudioGroup.java | 15 ++++++++------- jni/rtp/AudioGroup.cpp | 23 +++++++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index dc86082..37cc121 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -49,27 +49,28 @@ public class AudioGroup { synchronized void add(AudioStream stream, AudioCodec codec, int codecType, int dtmfType) { if (!mStreams.containsKey(stream)) { try { - int id = add(stream.getMode(), stream.dup(), + int socket = stream.dup(); + add(stream.getMode(), socket, stream.getRemoteAddress().getHostAddress(), stream.getRemotePort(), codec.name, codec.sampleRate, codec.sampleCount, codecType, dtmfType); - mStreams.put(stream, id); + mStreams.put(stream, socket); } catch (NullPointerException e) { throw new IllegalStateException(e); } } } - private native int add(int mode, int socket, String remoteAddress, int remotePort, + private native void add(int mode, int socket, String remoteAddress, int remotePort, String codecName, int sampleRate, int sampleCount, int codecType, int dtmfType); synchronized void remove(AudioStream stream) { - Integer id = mStreams.remove(stream); - if (id != null) { - remove(id); + Integer socket = mStreams.remove(stream); + if (socket != null) { + remove(socket); } } - private native void remove(int id); + private native void remove(int socket); /** * Sends a DTMF digit to every {@link AudioStream} in this group. Currently diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 63a60ea..08a8d1c 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -367,7 +367,7 @@ void AudioStream::decode(int tick) MSG_TRUNC | MSG_DONTWAIT); // Do we need to check SSRC, sequence, and timestamp? They are not - // reliable but at least they can be used to identity duplicates? + // reliable but at least they can be used to identify duplicates? if (length < 12 || length > (int)sizeof(buffer) || (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { LOGD("stream[%d] malformed packet", mSocket); @@ -697,6 +697,10 @@ bool AudioGroup::remove(int socket) for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) { AudioStream *target = stream->mNext; if (target->mSocket == socket) { + if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) { + LOGE("epoll_ctl: %s", strerror(errno)); + return false; + } stream->mNext = target->mNext; LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); delete target; @@ -800,7 +804,7 @@ bool AudioGroup::deviceLoop() static jfieldID gNative; static jfieldID gMode; -jint add(JNIEnv *env, jobject thiz, jint mode, +void add(JNIEnv *env, jobject thiz, jint mode, jint socket, jstring jRemoteAddress, jint remotePort, jstring jCodecName, jint sampleRate, jint sampleCount, jint codecType, jint dtmfType) @@ -813,7 +817,7 @@ jint add(JNIEnv *env, jobject thiz, jint mode, sockaddr_storage remote; if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { // Exception already thrown. - return -1; + goto error; } if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); @@ -821,12 +825,12 @@ jint add(JNIEnv *env, jobject thiz, jint mode, } if (!jCodecName) { jniThrowNullPointerException(env, "codecName"); - return -1; + goto error; } codecName = env->GetStringUTFChars(jCodecName, NULL); if (!codecName) { // Exception already thrown. - return -1; + goto error; } // Create audio stream. @@ -835,8 +839,10 @@ jint add(JNIEnv *env, jobject thiz, jint mode, codecType, dtmfType)) { jniThrowException(env, "java/lang/IllegalStateException", "cannot initialize audio stream"); + env->ReleaseStringUTFChars(jCodecName, codecName); goto error; } + env->ReleaseStringUTFChars(jCodecName, codecName); socket = -1; // Create audio group. @@ -860,16 +866,13 @@ jint add(JNIEnv *env, jobject thiz, jint mode, // Succeed. env->SetIntField(thiz, gNative, (int)group); - env->ReleaseStringUTFChars(jCodecName, codecName); - return socket; + return; error: delete group; delete stream; close(socket); env->SetIntField(thiz, gNative, NULL); - env->ReleaseStringUTFChars(jCodecName, codecName); - return -1; } void remove(JNIEnv *env, jobject thiz, jint socket) @@ -902,7 +905,7 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event) } JNINativeMethod gMethods[] = { - {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)I", (void *)add}, + {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)V", (void *)add}, {"remove", "(I)V", (void *)remove}, {"setMode", "(I)V", (void *)setMode}, {"sendDtmf", "(I)V", (void *)sendDtmf}, -- cgit v1.2.3 From 7dab0dc564087a4b8f90621cdfc8d730288fb201 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 24 Aug 2010 13:58:12 +0800 Subject: RTP: reduce the latency by overlapping AudioRecord and AudioTrack. Change-Id: I00d750ee514ef68d5b2a28bd1893417ed70ef1fc --- jni/rtp/AudioGroup.cpp | 81 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 08a8d1c..bb45a9a 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -548,10 +548,11 @@ bool AudioGroup::set(int sampleRate, int sampleCount) } LOGD("reported frame count: output %d, input %d", output, input); - output = (output + sampleCount - 1) / sampleCount * sampleCount; - input = (input + sampleCount - 1) / sampleCount * sampleCount; - if (input < output * 2) { - input = output * 2; + if (output < sampleCount * 2) { + output = sampleCount * 2; + } + if (input < sampleCount * 2) { + input = sampleCount * 2; } LOGD("adjusted frame count: output %d, input %d", output, input); @@ -587,7 +588,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) // Give device socket a reasonable timeout and buffer size. timeval tv; tv.tv_sec = 0; - tv.tv_usec = 1000 * sampleCount / sampleRate * 500; + tv.tv_usec = 1000 * sampleCount / sampleRate * 1000; if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { @@ -764,38 +765,62 @@ bool AudioGroup::deviceLoop() if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) { memset(output, 0, sizeof(output)); } - if (mTrack.write(output, sizeof(output)) != (int)sizeof(output)) { - LOGE("cannot write to AudioTrack"); - return false; - } - if (mMode != MUTED) { - uint32_t frameCount = mRecord.frameCount(); - AudioRecord::Buffer input; - input.frameCount = frameCount; + int16_t input[mSampleCount]; + int toWrite = mSampleCount; + int toRead = (mMode == MUTED) ? 0 : mSampleCount; + int chances = 100; - if (mRecord.obtainBuffer(&input, -1) != NO_ERROR) { - LOGE("cannot read from AudioRecord"); - return false; - } + while (--chances > 0 && (toWrite > 0 || toRead > 0)) { + if (toWrite > 0) { + AudioTrack::Buffer buffer; + buffer.frameCount = toWrite; - if (input.frameCount < (uint32_t)mSampleCount) { - input.frameCount = 0; - } else { - if (mMode == NORMAL) { - send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); - } else { - // TODO: Echo canceller runs here. - send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); + status_t status = mTrack.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + memcpy(buffer.i8, &output[mSampleCount - toWrite], buffer.size); + toWrite -= buffer.frameCount; + mTrack.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot write to AudioTrack"); + return false; } - if (input.frameCount < frameCount) { - input.frameCount = mSampleCount; + } + + if (toRead > 0) { + AudioRecord::Buffer buffer; + buffer.frameCount = mRecord.frameCount(); + + status_t status = mRecord.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + int count = (buffer.frameCount < toRead) ? + buffer.frameCount : toRead; + memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); + toRead -= count; + if (buffer.frameCount < mRecord.frameCount()) { + buffer.frameCount = count; + } + mRecord.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot read from AudioRecord"); + return false; } } + } - mRecord.releaseBuffer(&input); + if (!chances) { + LOGE("device loop timeout"); + return false; } + if (mMode != MUTED) { + if (mMode == NORMAL) { + send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); + } else { + // TODO: Echo canceller runs here. + send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); + } + } return true; } -- cgit v1.2.3 From dce965ae6f0c04fd37289b670cd07ee8efd5d5d4 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 24 Aug 2010 16:00:32 +0800 Subject: RTP: integrate the echo canceller from speex. Currently the filter_length is set to one second. Will change that when we have a better idea. Change-Id: Ia942a8fff00b096de8ff0049a448816ea9a68068 --- jni/rtp/Android.mk | 5 +++-- jni/rtp/AudioGroup.cpp | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index a364355..3bd85aa 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -32,10 +32,11 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libmedia -LOCAL_STATIC_LIBRARIES := +LOCAL_STATIC_LIBRARIES := libspeex LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) + $(JNI_H_INCLUDE) \ + external/speex/include LOCAL_CFLAGS += -fvisibility=hidden diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index bb45a9a..b5a4e22 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -40,6 +40,8 @@ #include #include +#include + #include "jni.h" #include "JNIHelp.h" @@ -445,6 +447,8 @@ private: int mDeviceSocket; AudioTrack mTrack; AudioRecord mRecord; + + SpeexEchoState *mEchoState; bool networkLoop(); bool deviceLoop(); @@ -506,6 +510,7 @@ AudioGroup::AudioGroup() mEventQueue = -1; mDtmfEvent = -1; mDeviceSocket = -1; + mEchoState = NULL; mNetworkThread = new NetworkThread(this); mDeviceThread = new DeviceThread(this); } @@ -518,6 +523,9 @@ AudioGroup::~AudioGroup() mRecord.stop(); close(mEventQueue); close(mDeviceSocket); + if (mEchoState) { + speex_echo_state_destroy(mEchoState); + } while (mChain) { AudioStream *next = mChain->mNext; delete mChain; @@ -566,7 +574,8 @@ bool AudioGroup::set(int sampleRate, int sampleCount) } LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); - // TODO: initialize echo canceler here. + // Initialize echo canceller. + mEchoState = speex_echo_state_init(sampleCount, sampleRate); // Create device socket. int pair[2]; @@ -633,6 +642,7 @@ bool AudioGroup::setMode(int mode) if (mode == MUTED) { mRecord.stop(); } else { + speex_echo_state_reset(mEchoState); mRecord.start(); } @@ -793,7 +803,7 @@ bool AudioGroup::deviceLoop() status_t status = mRecord.obtainBuffer(&buffer, 1); if (status == NO_ERROR) { - int count = (buffer.frameCount < toRead) ? + int count = ((int)buffer.frameCount < toRead) ? buffer.frameCount : toRead; memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); toRead -= count; @@ -817,8 +827,9 @@ bool AudioGroup::deviceLoop() if (mMode == NORMAL) { send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); } else { - // TODO: Echo canceller runs here. - send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); + int16_t result[mSampleCount]; + speex_echo_cancellation(mEchoState, input, output, result); + send(mDeviceSocket, result, sizeof(result), MSG_DONTWAIT); } } return true; -- cgit v1.2.3 From 0ca131a2c0474ace43678a130eeee0d491a5fac4 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 18 Aug 2010 14:37:59 +0800 Subject: Add confcall management to SIP calls and fix the bug of re-assigning connectTime's in SipConnection, and adding synchronization for SipPhone to be thread-safe, and set normal audio mode when call not on hold instead of on hold in SipAudioCallImpl, and fix re-entrance problem in CallManager.setAudioMode() for in-call mode. Change-Id: I54f39dab052062de1ce141e5358d892d30453a3a --- java/android/net/sip/SipAudioCall.java | 12 ++++++- java/android/net/sip/SipAudioCallImpl.java | 51 ++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index f4be839..3cdd114 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -244,7 +244,8 @@ public interface SipAudioCall { * Also, the {@code AudioStream} may change its group during a call (e.g., * after the call is held/un-held). Finally, the {@code AudioGroup} object * returned by this method is undefined after the call ends or the - * {@link #close} method is called. + * {@link #close} method is called. If a group object is set by + * {@link #setAudioGroup(AudioGroup)}, then this method returns that object. * * @return the {@link AudioGroup} object or null if the RTP stream has not * yet been set up @@ -252,6 +253,15 @@ public interface SipAudioCall { */ AudioGroup getAudioGroup(); + /** + * Sets the {@link AudioGroup} object which the {@link AudioStream} object + * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object + * will be dynamically created when needed. + * + * @see #getAudioStream + */ + void setAudioGroup(AudioGroup audioGroup); + /** * Checks if the call is established. * diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 7161309..b8ac6d7 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -70,7 +70,8 @@ public class SipAudioCallImpl extends SipSessionAdapter private ISipSession mSipSession; private SdpSessionDescription mPeerSd; - private AudioStream mRtpSession; + private AudioStream mAudioStream; + private AudioGroup mAudioGroup; private SdpSessionDescription.AudioCodec mCodec; private long mSessionId = -1L; // SDP session ID private boolean mInCall = false; @@ -505,11 +506,19 @@ public class SipAudioCallImpl extends SipSessionAdapter } public synchronized AudioStream getAudioStream() { - return mRtpSession; + return mAudioStream; } public synchronized AudioGroup getAudioGroup() { - return ((mRtpSession == null) ? null : mRtpSession.getAudioGroup()); + if (mAudioGroup != null) return mAudioGroup; + return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup()); + } + + public synchronized void setAudioGroup(AudioGroup group) { + if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) { + mAudioStream.join(group); + } + mAudioGroup = group; } private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) { @@ -561,7 +570,7 @@ public class SipAudioCallImpl extends SipSessionAdapter // TODO: get sample rate from sdp mCodec = getCodec(peerSd); - AudioStream audioStream = mRtpSession; + AudioStream audioStream = mAudioStream; audioStream.associate(InetAddress.getByName(peerMediaAddress), peerMediaPort); audioStream.setCodec(convert(mCodec), mCodec.payloadType); @@ -580,7 +589,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.d(TAG, " not sending"); audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); } - } else { + /* 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. @@ -590,14 +599,22 @@ public class SipAudioCallImpl extends SipSessionAdapter .setMode(AudioManager.MODE_NORMAL); } - AudioGroup audioGroup = new AudioGroup(); - audioStream.join(audioGroup); + // AudioGroup logic: + AudioGroup audioGroup = getAudioGroup(); if (mHold) { - audioGroup.setMode(AudioGroup.MODE_ON_HOLD); - } else if (mMuted) { - audioGroup.setMode(AudioGroup.MODE_MUTED); + 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 { - audioGroup.setMode(AudioGroup.MODE_NORMAL); + if (audioGroup == null) audioGroup = new AudioGroup(); + audioStream.join(audioGroup); + if (mMuted) { + audioGroup.setMode(AudioGroup.MODE_MUTED); + } else { + audioGroup.setMode(AudioGroup.MODE_NORMAL); + } } } catch (Exception e) { Log.e(TAG, "call()", e); @@ -606,20 +623,20 @@ public class SipAudioCallImpl extends SipSessionAdapter private void stopCall(boolean releaseSocket) { Log.d(TAG, "stop audiocall"); - if (mRtpSession != null) { - mRtpSession.join(null); + if (mAudioStream != null) { + mAudioStream.join(null); if (releaseSocket) { - mRtpSession.release(); - mRtpSession = null; + mAudioStream.release(); + mAudioStream = null; } } } private int getLocalMediaPort() { - if (mRtpSession != null) return mRtpSession.getLocalPort(); + if (mAudioStream != null) return mAudioStream.getLocalPort(); try { - AudioStream s = mRtpSession = + AudioStream s = mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); return s.getLocalPort(); } catch (IOException e) { -- cgit v1.2.3 From 5fe3b0b82425192fc0079ccda44bc1c2747f2aec Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 23 Aug 2010 21:36:14 +0800 Subject: SipProfile: add isOutgoingCallAllowed() and new builder constructor Change-Id: I7ced47079fd2b00c7160b152eb4c1d34399e39dc --- java/android/net/sip/SipProfile.java | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index e71c293..ec8d0ed 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -35,7 +35,7 @@ import javax.sip.address.URI; * Class containing a SIP account, domain and server information. * @hide */ -public class SipProfile implements Parcelable, Serializable { +public class SipProfile implements Parcelable, Serializable, Cloneable { private static final long serialVersionUID = 1L; private static final int DEFAULT_PORT = 5060; private Address mAddress; @@ -46,6 +46,7 @@ public class SipProfile implements Parcelable, Serializable { private String mProfileName; private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; + private boolean mAllowOutgoingCall = false; /** @hide */ public static final Parcelable.Creator CREATOR = @@ -78,6 +79,23 @@ public class SipProfile implements Parcelable, Serializable { } } + /** + * Creates a builder based on the given profile. + */ + public Builder(SipProfile profile) { + if (profile == null) throw new NullPointerException(); + try { + mProfile = (SipProfile) profile.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("should not occur", e); + } + mProfile.mAddress = null; + mUri = profile.getUri(); + mUri.setUserPassword(profile.getPassword()); + mDisplayName = profile.getDisplayName(); + mProxyAddress = profile.getProxyAddress(); + } + /** * Constructor. * @@ -225,6 +243,18 @@ public class SipProfile implements Parcelable, Serializable { return this; } + /** + * Sets the allow-outgoing-call flag. + * + * @param flag true if allowing to make outgoing call on the profile; + * false otherwise + * @return this builder object + */ + public Builder setOutgoingCallAllowed(boolean flag) { + mProfile.mAllowOutgoingCall = flag; + return this; + } + /** * Builds and returns the SIP profile object. * @@ -262,6 +292,7 @@ public class SipProfile implements Parcelable, Serializable { mProfileName = in.readString(); mSendKeepAlive = (in.readInt() == 0) ? false : true; mAutoRegistration = (in.readInt() == 0) ? false : true; + mAllowOutgoingCall = (in.readInt() == 0) ? false : true; } /** @hide */ @@ -274,6 +305,7 @@ public class SipProfile implements Parcelable, Serializable { out.writeString(mProfileName); out.writeInt(mSendKeepAlive ? 1 : 0); out.writeInt(mAutoRegistration ? 1 : 0); + out.writeInt(mAllowOutgoingCall ? 1 : 0); } /** @hide */ @@ -398,4 +430,11 @@ public class SipProfile implements Parcelable, Serializable { public boolean getAutoRegistration() { return mAutoRegistration; } + + /** + * Returns true if allowing to make outgoing calls on this profile. + */ + public boolean isOutgoingCallAllowed() { + return mAllowOutgoingCall; + } } -- cgit v1.2.3 From bfe4c0e1cdd340ca8a3d337305742d9108d0f2a5 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Wed, 25 Aug 2010 19:02:18 +0800 Subject: Add dynamic uid info for tracking the sip service usage. Change-Id: Ibc340401b63799326b08aee6eba602a3e753b13f --- java/android/net/sip/SipProfile.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index ec8d0ed..6c99141 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -47,6 +47,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; private boolean mAllowOutgoingCall = false; + private int mCallingUid = -1; /** @hide */ public static final Parcelable.Creator CREATOR = @@ -293,6 +294,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mSendKeepAlive = (in.readInt() == 0) ? false : true; mAutoRegistration = (in.readInt() == 0) ? false : true; mAllowOutgoingCall = (in.readInt() == 0) ? false : true; + mCallingUid = in.readInt(); } /** @hide */ @@ -306,6 +308,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeInt(mSendKeepAlive ? 1 : 0); out.writeInt(mAutoRegistration ? 1 : 0); out.writeInt(mAllowOutgoingCall ? 1 : 0); + out.writeInt(mCallingUid); } /** @hide */ @@ -437,4 +440,20 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { public boolean isOutgoingCallAllowed() { return mAllowOutgoingCall; } + + /** + * Sets the calling process's Uid in the sip service. + * @hide + */ + public void setCallingUid(int uid) { + mCallingUid = uid; + } + + /** + * Gets the calling process's Uid in the sip settings. + * @hide + */ + public int getCallingUid() { + return mCallingUid; + } } -- cgit v1.2.3 From ac2e214d6a09850cf61de8a482032dc0e0ccdde0 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 26 Aug 2010 10:33:09 +0800 Subject: Revert "RTP: integrate the echo canceller from speex." This reverts commit 4ae6ec428f3570b9020b35ada6a62f94af66d888. --- jni/rtp/Android.mk | 5 ++--- jni/rtp/AudioGroup.cpp | 19 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 3bd85aa..a364355 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -32,11 +32,10 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libmedia -LOCAL_STATIC_LIBRARIES := libspeex +LOCAL_STATIC_LIBRARIES := LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) \ - external/speex/include + $(JNI_H_INCLUDE) LOCAL_CFLAGS += -fvisibility=hidden diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index b5a4e22..bb45a9a 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -40,8 +40,6 @@ #include #include -#include - #include "jni.h" #include "JNIHelp.h" @@ -447,8 +445,6 @@ private: int mDeviceSocket; AudioTrack mTrack; AudioRecord mRecord; - - SpeexEchoState *mEchoState; bool networkLoop(); bool deviceLoop(); @@ -510,7 +506,6 @@ AudioGroup::AudioGroup() mEventQueue = -1; mDtmfEvent = -1; mDeviceSocket = -1; - mEchoState = NULL; mNetworkThread = new NetworkThread(this); mDeviceThread = new DeviceThread(this); } @@ -523,9 +518,6 @@ AudioGroup::~AudioGroup() mRecord.stop(); close(mEventQueue); close(mDeviceSocket); - if (mEchoState) { - speex_echo_state_destroy(mEchoState); - } while (mChain) { AudioStream *next = mChain->mNext; delete mChain; @@ -574,8 +566,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) } LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); - // Initialize echo canceller. - mEchoState = speex_echo_state_init(sampleCount, sampleRate); + // TODO: initialize echo canceler here. // Create device socket. int pair[2]; @@ -642,7 +633,6 @@ bool AudioGroup::setMode(int mode) if (mode == MUTED) { mRecord.stop(); } else { - speex_echo_state_reset(mEchoState); mRecord.start(); } @@ -803,7 +793,7 @@ bool AudioGroup::deviceLoop() status_t status = mRecord.obtainBuffer(&buffer, 1); if (status == NO_ERROR) { - int count = ((int)buffer.frameCount < toRead) ? + int count = (buffer.frameCount < toRead) ? buffer.frameCount : toRead; memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); toRead -= count; @@ -827,9 +817,8 @@ bool AudioGroup::deviceLoop() if (mMode == NORMAL) { send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); } else { - int16_t result[mSampleCount]; - speex_echo_cancellation(mEchoState, input, output, result); - send(mDeviceSocket, result, sizeof(result), MSG_DONTWAIT); + // TODO: Echo canceller runs here. + send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); } } return true; -- cgit v1.2.3 From 9248b2a99ffa82713a8a6d71d0b68667b8f187e7 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 26 Aug 2010 15:05:48 +0800 Subject: Add Wifi High Perf. mode during a call. To prevent the wifi from entering low-power mode due to the screen off triggered by the proximity sensor. Change-Id: I490bc594d800bc30c256e52ef3bce08bf86bc7b1 --- java/android/net/sip/SipAudioCallImpl.java | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index b8ac6d7..5789cd4 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -36,6 +36,7 @@ import android.net.sip.SipManager; import android.net.sip.SipProfile; import android.net.sip.SipSessionAdapter; import android.net.sip.SipSessionState; +import android.net.wifi.WifiManager; import android.os.Message; import android.os.RemoteException; import android.os.Vibrator; @@ -84,10 +85,13 @@ public class SipAudioCallImpl extends SipSessionAdapter private ToneGenerator mRingbackTone; private SipProfile mPendingCallRequest; + private WifiManager mWm; + private WifiManager.WifiLock mWifiHighPerfLock; public SipAudioCallImpl(Context context, SipProfile localProfile) { mContext = context; mLocalProfile = localProfile; + mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } public void setListener(SipAudioCall.Listener listener) { @@ -422,6 +426,28 @@ public class SipAudioCallImpl extends SipSessionAdapter } } + 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; + } + private SessionDescription createContinueSessionDescription() { return createSdpBuilder(true, mCodec).build(); } @@ -556,7 +582,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private void startCall(SdpSessionDescription peerSd) { stopCall(DONT_RELEASE_SOCKET); - + if (isWifiOn()) grabWifiHighPerfLock(); mPeerSd = peerSd; String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); // TODO: handle multiple media fields @@ -623,6 +649,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private void stopCall(boolean releaseSocket) { Log.d(TAG, "stop audiocall"); + releaseWifiHighPerfLock(); if (mAudioStream != null) { mAudioStream.join(null); -- cgit v1.2.3 From 450c007b666315d814aae13752784c5a1e88b265 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 26 Aug 2010 16:36:01 +0800 Subject: Revert "Add Wifi High Perf. mode during a call." This reverts commit 0858806ffcb9ff34725abb79106aa1de27d1bf60. Change-Id: I0432ac7af6e0becf196728190b71dfe82ce63cd0 --- java/android/net/sip/SipAudioCallImpl.java | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 5789cd4..b8ac6d7 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -36,7 +36,6 @@ import android.net.sip.SipManager; import android.net.sip.SipProfile; import android.net.sip.SipSessionAdapter; import android.net.sip.SipSessionState; -import android.net.wifi.WifiManager; import android.os.Message; import android.os.RemoteException; import android.os.Vibrator; @@ -85,13 +84,10 @@ public class SipAudioCallImpl extends SipSessionAdapter private ToneGenerator mRingbackTone; private SipProfile mPendingCallRequest; - private WifiManager mWm; - private WifiManager.WifiLock mWifiHighPerfLock; public SipAudioCallImpl(Context context, SipProfile localProfile) { mContext = context; mLocalProfile = localProfile; - mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } public void setListener(SipAudioCall.Listener listener) { @@ -426,28 +422,6 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - 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; - } - private SessionDescription createContinueSessionDescription() { return createSdpBuilder(true, mCodec).build(); } @@ -582,7 +556,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private void startCall(SdpSessionDescription peerSd) { stopCall(DONT_RELEASE_SOCKET); - if (isWifiOn()) grabWifiHighPerfLock(); + mPeerSd = peerSd; String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); // TODO: handle multiple media fields @@ -649,7 +623,6 @@ public class SipAudioCallImpl extends SipSessionAdapter private void stopCall(boolean releaseSocket) { Log.d(TAG, "stop audiocall"); - releaseWifiHighPerfLock(); if (mAudioStream != null) { mAudioStream.join(null); -- cgit v1.2.3 From 69d66b75112e2763bf8bce718c22c7baa1efac7d Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 27 Aug 2010 18:08:19 +0800 Subject: Add software features for SIP and VOIP and block SipService creation and SIP API if the feature is not available. Change-Id: Icf780af1ac20dda4d8180cea3e5b20e21a8350bc --- data/etc/android.software.sip.voip.xml | 21 +++++++++++++++++++++ java/android/net/sip/SipManager.java | 33 ++++++++++++++++++++++++--------- 2 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 data/etc/android.software.sip.voip.xml diff --git a/data/etc/android.software.sip.voip.xml b/data/etc/android.software.sip.voip.xml new file mode 100644 index 0000000..edd06c1 --- /dev/null +++ b/data/etc/android.software.sip.voip.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 287a13a..40792b9 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -18,6 +18,7 @@ package android.net.sip; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; @@ -69,22 +70,36 @@ public class SipManager { private ISipService mSipService; /** - * Creates a manager instance and initializes the background SIP service. - * Will be removed once the SIP service is integrated into framework. + * Gets a manager instance. Returns null if SIP API is not supported. * - * @param context context to start the SIP service - * @return the manager instance + * @param context application context for checking if SIP API is supported + * @return the manager instance or null if SIP API is not supported */ - public static SipManager getInstance(final Context context) { - final SipManager manager = new SipManager(); - manager.createSipService(context); - return manager; + public static SipManager getInstance(Context context) { + return (isApiSupported(context) ? new SipManager() : null); + } + + /** + * Returns true if the SIP API is supported by the system. + */ + public static boolean isApiSupported(Context context) { + return context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_SIP); + } + + /** + * Returns true if the system supports SIP-based VoIP. + */ + public static boolean isVoipSupported(Context context) { + return context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); } private SipManager() { + createSipService(); } - private void createSipService(Context context) { + private void createSipService() { if (mSipService != null) return; IBinder b = ServiceManager.getService(Context.SIP_SERVICE); mSipService = ISipService.Stub.asInterface(b); -- cgit v1.2.3 From 8ea6eb25a01cc6429172655614ca4c560b2607d3 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 1 Sep 2010 09:15:10 +0800 Subject: SipProfile: remove outgoingCallAllowed flag. Change-Id: I37a215bafce57adf6911c81fd38db324bac686ec --- java/android/net/sip/SipProfile.java | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 6c99141..aa2da75 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -46,8 +46,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { private String mProfileName; private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; - private boolean mAllowOutgoingCall = false; - private int mCallingUid = -1; + private transient int mCallingUid = 0; /** @hide */ public static final Parcelable.Creator CREATOR = @@ -244,18 +243,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { return this; } - /** - * Sets the allow-outgoing-call flag. - * - * @param flag true if allowing to make outgoing call on the profile; - * false otherwise - * @return this builder object - */ - public Builder setOutgoingCallAllowed(boolean flag) { - mProfile.mAllowOutgoingCall = flag; - return this; - } - /** * Builds and returns the SIP profile object. * @@ -293,7 +280,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mProfileName = in.readString(); mSendKeepAlive = (in.readInt() == 0) ? false : true; mAutoRegistration = (in.readInt() == 0) ? false : true; - mAllowOutgoingCall = (in.readInt() == 0) ? false : true; mCallingUid = in.readInt(); } @@ -307,7 +293,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeString(mProfileName); out.writeInt(mSendKeepAlive ? 1 : 0); out.writeInt(mAutoRegistration ? 1 : 0); - out.writeInt(mAllowOutgoingCall ? 1 : 0); out.writeInt(mCallingUid); } @@ -434,13 +419,6 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { return mAutoRegistration; } - /** - * Returns true if allowing to make outgoing calls on this profile. - */ - public boolean isOutgoingCallAllowed() { - return mAllowOutgoingCall; - } - /** * Sets the calling process's Uid in the sip service. * @hide -- cgit v1.2.3 From 6d0ef774a2492b0996ded3a43c300c7f72a94897 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 2 Sep 2010 22:15:26 +0800 Subject: SipService: reduce the usage of javax.sdp.*. After this change, SipAudioCallImpl is the only place still using it. Change-Id: I5693bffa54f9e19cbfa70b45dfcf40fba04dedbb --- java/android/net/sip/ISipSession.aidl | 7 +++---- java/android/net/sip/ISipSessionListener.aidl | 4 ++-- java/android/net/sip/SdpSessionDescription.java | 4 ++-- java/android/net/sip/SipAudioCall.java | 4 ++-- java/android/net/sip/SipAudioCallImpl.java | 26 +++++++++---------------- java/android/net/sip/SipManager.java | 14 ++++++------- java/android/net/sip/SipSessionAdapter.java | 6 +++--- 7 files changed, 27 insertions(+), 38 deletions(-) diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl index fbcb056..1a23527 100644 --- a/java/android/net/sip/ISipSession.aidl +++ b/java/android/net/sip/ISipSession.aidl @@ -115,8 +115,7 @@ interface ISipSession { * @param sessionDescription the session description of this call * @see ISipSessionListener */ - void makeCall(in SipProfile callee, - in SessionDescription sessionDescription); + void makeCall(in SipProfile callee, String sessionDescription); /** * Answers an incoming call with the specified session description. The @@ -125,7 +124,7 @@ interface ISipSession { * * @param sessionDescription the session description to answer this call */ - void answerCall(in SessionDescription sessionDescription); + void answerCall(String sessionDescription); /** * Ends an established call, terminates an outgoing call or rejects an @@ -143,5 +142,5 @@ interface ISipSession { * * @param sessionDescription the new session description */ - void changeCall(in SessionDescription sessionDescription); + void changeCall(String sessionDescription); } diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl index 8570958..c552a57 100644 --- a/java/android/net/sip/ISipSessionListener.aidl +++ b/java/android/net/sip/ISipSessionListener.aidl @@ -39,7 +39,7 @@ interface ISipSessionListener { * @param sessionDescription the caller's session description */ void onRinging(in ISipSession session, in SipProfile caller, - in byte[] sessionDescription); + String sessionDescription); /** * Called when a RINGING response is received for the INVITE request sent @@ -55,7 +55,7 @@ interface ISipSessionListener { * @param sessionDescription the peer's session description */ void onCallEstablished(in ISipSession session, - in byte[] sessionDescription); + String sessionDescription); /** * Called when the session is terminated. diff --git a/java/android/net/sip/SdpSessionDescription.java b/java/android/net/sip/SdpSessionDescription.java index 0c29935..f6ae837 100644 --- a/java/android/net/sip/SdpSessionDescription.java +++ b/java/android/net/sip/SdpSessionDescription.java @@ -186,8 +186,8 @@ public class SdpSessionDescription extends SessionDescription { } } - public SdpSessionDescription build() { - return mSdp; + public String build() { + return mSdp.toString(); } } diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 3cdd114..8254543 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -168,9 +168,9 @@ public interface SipAudioCall { * Attaches an incoming call to this call object. * * @param session the session that receives the incoming call - * @param sdp the session description of the incoming call + * @param sessionDescription the session description of the incoming call */ - void attachCall(ISipSession session, SdpSessionDescription sdp) + void attachCall(ISipSession session, String sessionDescription) throws SipException; /** Ends a call. */ diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 5789cd4..a312f83 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -28,14 +28,6 @@ import android.net.rtp.AudioCodec; import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.net.rtp.RtpStream; -import android.net.sip.ISipSession; -import android.net.sip.SdpSessionDescription; -import android.net.sip.SessionDescription; -import android.net.sip.SipAudioCall; -import android.net.sip.SipManager; -import android.net.sip.SipProfile; -import android.net.sip.SipSessionAdapter; -import android.net.sip.SipSessionState; import android.net.wifi.WifiManager; import android.os.Message; import android.os.RemoteException; @@ -200,7 +192,7 @@ public class SipAudioCallImpl extends SipSessionAdapter @Override public synchronized void onRinging(ISipSession session, - SipProfile peerProfile, byte[] sessionDescription) { + SipProfile peerProfile, String sessionDescription) { try { if ((mSipSession == null) || !mInCall || !session.getCallId().equals(mSipSession.getCallId())) { @@ -222,7 +214,7 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - private synchronized void establishCall(byte[] sessionDescription) { + private synchronized void establishCall(String sessionDescription) { stopRingbackTone(); stopRinging(); try { @@ -238,7 +230,7 @@ public class SipAudioCallImpl extends SipSessionAdapter @Override public void onCallEstablished(ISipSession session, - byte[] sessionDescription) { + String sessionDescription) { establishCall(sessionDescription); Listener listener = mListener; if (listener != null) { @@ -316,10 +308,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } public synchronized void attachCall(ISipSession session, - SdpSessionDescription sdp) throws SipException { + String sessionDescription) throws SipException { mSipSession = session; - mPeerSd = sdp; try { + mPeerSd = new SdpSessionDescription(sessionDescription); session.setListener(this); } catch (Throwable e) { Log.e(TAG, "attachCall()", e); @@ -394,12 +386,12 @@ public class SipAudioCallImpl extends SipSessionAdapter if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); } - private SessionDescription createOfferSessionDescription() { + private String createOfferSessionDescription() { AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs(); return createSdpBuilder(true, convert(codecs)).build(); } - private SessionDescription createAnswerSessionDescription() { + private String createAnswerSessionDescription() { try { // choose an acceptable media from mPeerSd to answer SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd); @@ -416,7 +408,7 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - private SessionDescription createHoldSessionDescription() { + private String createHoldSessionDescription() { try { return createSdpBuilder(false, mCodec) .addMediaAttribute(AUDIO, "sendonly", (String) null) @@ -448,7 +440,7 @@ public class SipAudioCallImpl extends SipSessionAdapter return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; } - private SessionDescription createContinueSessionDescription() { + private String createContinueSessionDescription() { return createSdpBuilder(true, mCodec).build(); } diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 40792b9..700fb4e 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -298,21 +298,19 @@ public class SipManager { throw new SipException("Call ID missing in incoming call intent"); } - byte[] offerSd = getOfferSessionDescription(incomingCallIntent); + String offerSd = getOfferSessionDescription(incomingCallIntent); if (offerSd == null) { throw new SipException("Session description missing in incoming " + "call intent"); } try { - SdpSessionDescription sdp = new SdpSessionDescription(offerSd); - ISipSession session = mSipService.getPendingSession(callId); if (session == null) return null; SipAudioCall call = new SipAudioCallImpl( context, session.getLocalProfile()); call.setRingtoneEnabled(ringtoneEnabled); - call.attachCall(session, sdp); + call.attachCall(session, offerSd); call.setListener(listener); return call; } catch (Throwable t) { @@ -329,7 +327,7 @@ public class SipManager { public static boolean isIncomingCallIntent(Intent intent) { if (intent == null) return false; String callId = getCallId(intent); - byte[] offerSd = getOfferSessionDescription(intent); + String offerSd = getOfferSessionDescription(intent); return ((callId != null) && (offerSd != null)); } @@ -351,8 +349,8 @@ public class SipManager { * @return the offer session description or null if the intent does not * have it */ - public static byte[] getOfferSessionDescription(Intent incomingCallIntent) { - return incomingCallIntent.getByteArrayExtra(OFFER_SD_KEY); + public static String getOfferSessionDescription(Intent incomingCallIntent) { + return incomingCallIntent.getStringExtra(OFFER_SD_KEY); } /** @@ -365,7 +363,7 @@ public class SipManager { * @hide */ public static Intent createIncomingCallBroadcast(String action, - String callId, byte[] sessionDescription) { + String callId, String sessionDescription) { Intent intent = new Intent(action); intent.putExtra(CALL_ID_KEY, callId); intent.putExtra(OFFER_SD_KEY, sessionDescription); diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java index cfb71d7..770d4eb 100644 --- a/java/android/net/sip/SipSessionAdapter.java +++ b/java/android/net/sip/SipSessionAdapter.java @@ -26,14 +26,14 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { } public void onRinging(ISipSession session, SipProfile caller, - byte[] sessionDescription) { + String sessionDescription) { } public void onRingingBack(ISipSession session) { } - public void onCallEstablished( - ISipSession session, byte[] sessionDescription) { + public void onCallEstablished(ISipSession session, + String sessionDescription) { } public void onCallEnded(ISipSession session) { -- cgit v1.2.3 From 0d889a979d7e2e6f21de72de0c45a9c8ffa930e6 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 3 Sep 2010 10:19:23 +0800 Subject: SipManager: always return true for SIP API and VOIP support query. Change-Id: I397a804e0aa598aee77a8ce28ada1b11e10fbaea http://b/issue?id=2972054 --- java/android/net/sip/SipManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 700fb4e..beec8fe 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -83,16 +83,22 @@ public class SipManager { * Returns true if the SIP API is supported by the system. */ public static boolean isApiSupported(Context context) { + return true; + /* return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP); + */ } /** * Returns true if the system supports SIP-based VoIP. */ public static boolean isVoipSupported(Context context) { + return true; + /* return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); + */ } private SipManager() { -- cgit v1.2.3 From f8c9085b3658cfa72d99e1c9e1b1755c10898f28 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 3 Sep 2010 10:19:23 +0800 Subject: SipManager: always return true for SIP API and VOIP support query. Change-Id: I397a804e0aa598aee77a8ce28ada1b11e10fbaea http://b/issue?id=2972054 --- java/android/net/sip/SipManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 700fb4e..beec8fe 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -83,16 +83,22 @@ public class SipManager { * Returns true if the SIP API is supported by the system. */ public static boolean isApiSupported(Context context) { + return true; + /* return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP); + */ } /** * Returns true if the system supports SIP-based VoIP. */ public static boolean isVoipSupported(Context context) { + return true; + /* return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); + */ } private SipManager() { -- cgit v1.2.3 From a7f73fb2550935cc78c643b7faf25ebacf16b94f Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 8 Sep 2010 09:56:02 +0800 Subject: RTP: prevent buffer overflow in AudioRecord. This change simply reduces the receive timeout of DeviceSocket. It works because AudioRecord will block us till there is enough data, which makes AudioSocket overlap AudioRecord. Change-Id: I4700224fb407e148ef359a9d99279e10240128d0 --- jni/rtp/AudioGroup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index bb45a9a..3433dcf 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -588,7 +588,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) // Give device socket a reasonable timeout and buffer size. timeval tv; tv.tv_sec = 0; - tv.tv_usec = 1000 * sampleCount / sampleRate * 1000; + tv.tv_usec = 1000 * sampleCount / sampleRate * 500; if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { @@ -793,7 +793,7 @@ bool AudioGroup::deviceLoop() status_t status = mRecord.obtainBuffer(&buffer, 1); if (status == NO_ERROR) { - int count = (buffer.frameCount < toRead) ? + int count = ((int)buffer.frameCount < toRead) ? buffer.frameCount : toRead; memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); toRead -= count; -- cgit v1.2.3 From 2bd51a23644fa0d5a460a4a939e95d5d4e85b891 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 9 Sep 2010 20:07:14 +0800 Subject: SIP: add SipErrorCode for error feedback. Change-Id: I8b071d4933479b780a403d0bfa30511f4c23ca8f --- java/android/net/sip/ISipSessionListener.aidl | 12 +++--- java/android/net/sip/SipAudioCall.java | 6 ++- java/android/net/sip/SipAudioCallImpl.java | 17 ++++----- java/android/net/sip/SipErrorCode.java | 45 +++++++++++++++++++++++ java/android/net/sip/SipRegistrationListener.java | 4 +- java/android/net/sip/SipSessionAdapter.java | 9 ++--- 6 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 java/android/net/sip/SipErrorCode.java diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl index c552a57..0a6220b 100644 --- a/java/android/net/sip/ISipSessionListener.aidl +++ b/java/android/net/sip/ISipSessionListener.aidl @@ -76,20 +76,20 @@ interface ISipSessionListener { * termination. * * @param session the session object that carries out the transaction - * @param errorClass name of the exception class + * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onError(in ISipSession session, String errorClass, + void onError(in ISipSession session, String errorCode, String errorMessage); /** * Called when an error occurs during session modification negotiation. * * @param session the session object that carries out the transaction - * @param errorClass name of the exception class + * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onCallChangeFailed(in ISipSession session, String errorClass, + void onCallChangeFailed(in ISipSession session, String errorCode, String errorMessage); /** @@ -111,10 +111,10 @@ interface ISipSessionListener { * Called when the registration fails. * * @param session the session object that carries out the transaction - * @param errorClass name of the exception class + * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onRegistrationFailed(in ISipSession session, String errorClass, + void onRegistrationFailed(in ISipSession session, String errorCode, String errorMessage); /** diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 8254543..02f82b3 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -90,9 +90,10 @@ public interface SipAudioCall { * Called when an error occurs. * * @param call the call object that carries out the audio call + * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onError(SipAudioCall call, String errorMessage); + void onError(SipAudioCall call, String errorCode, String errorMessage); } /** @@ -126,7 +127,8 @@ public interface SipAudioCall { public void onCallHeld(SipAudioCall call) { onChanged(call); } - public void onError(SipAudioCall call, String errorMessage) { + public void onError(SipAudioCall call, String errorCode, + String errorMessage) { onChanged(call); } } diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index a312f83..2e2ca5b 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -108,7 +108,8 @@ public class SipAudioCallImpl extends SipSessionAdapter listener.onCalling(this); break; default: - listener.onError(this, "wrong state to attach call: " + state); + listener.onError(this, SipErrorCode.CLIENT_ERROR.toString(), + "wrong state to attach call: " + state); } } catch (Throwable t) { Log.e(TAG, "setListener()", t); @@ -275,14 +276,13 @@ public class SipAudioCallImpl extends SipSessionAdapter } @Override - public void onCallChangeFailed(ISipSession session, - String className, String message) { + public void onCallChangeFailed(ISipSession session, String errorCode, + String message) { Log.d(TAG, "sip call change failed: " + message); Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, - className + ": " + message); + listener.onError(SipAudioCallImpl.this, errorCode, message); } catch (Throwable t) { Log.e(TAG, "onCallBusy()", t); } @@ -290,17 +290,16 @@ public class SipAudioCallImpl extends SipSessionAdapter } @Override - public void onError(ISipSession session, String className, + public void onError(ISipSession session, String errorCode, String message) { - Log.d(TAG, "sip session error: " + className + ": " + message); + Log.d(TAG, "sip session error: " + errorCode + ": " + message); synchronized (this) { if (!isInCall()) close(true); } Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, - className + ": " + message); + listener.onError(SipAudioCallImpl.this, errorCode, message); } catch (Throwable t) { Log.e(TAG, "onError()", t); } diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java new file mode 100644 index 0000000..2eb67e8 --- /dev/null +++ b/java/android/net/sip/SipErrorCode.java @@ -0,0 +1,45 @@ +/* + * 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 error code returned in + * {@link SipRegistrationListener#onRegistrationFailed(String, String, String)}, + * {@link ISipSessionListener#onError(ISipSession, String, String)}, + * {@link ISipSessionListener#onCallChangeFailed(ISipSession, String, String)} and + * {@link ISipSessionListener#onRegistrationFailed(ISipSession, String, String)}. + * @hide + */ +public enum SipErrorCode { + /** When some socket error occurs. */ + SOCKET_ERROR, + + /** When server responds with an error. */ + SERVER_ERROR, + + /** When some error occurs on the device, possibly due to a bug. */ + CLIENT_ERROR, + + /** When the transaction gets timed out. */ + TIME_OUT, + + /** When the remote URI is not valid. */ + INVALID_REMOTE_URI, + + /** When invalid credentials are provided. */ + INVALID_CREDENTIALS; +} diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index 63faaf8..22488d7 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -40,9 +40,9 @@ public interface SipRegistrationListener { * Called when the registration fails. * * @param localProfileUri the URI string of the SIP profile to register with - * @param errorClass name of the exception class + * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onRegistrationFailed(String localProfileUri, String errorClass, + void onRegistrationFailed(String localProfileUri, String errorCode, String errorMessage); } diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java index 770d4eb..6020f2c 100644 --- a/java/android/net/sip/SipSessionAdapter.java +++ b/java/android/net/sip/SipSessionAdapter.java @@ -42,14 +42,11 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onCallBusy(ISipSession session) { } - public void onCallChanged(ISipSession session, byte[] sessionDescription) { - } - - public void onCallChangeFailed(ISipSession session, String className, + public void onCallChangeFailed(ISipSession session, String errorCode, String message) { } - public void onError(ISipSession session, String className, String message) { + public void onError(ISipSession session, String errorCode, String message) { } public void onRegistering(ISipSession session) { @@ -58,7 +55,7 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onRegistrationDone(ISipSession session, int duration) { } - public void onRegistrationFailed(ISipSession session, String className, + public void onRegistrationFailed(ISipSession session, String errorCode, String message) { } -- cgit v1.2.3 From bba28d83647438df39b55d59161e3f69ff8209f6 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 13 Sep 2010 16:50:12 +0800 Subject: SIP: remove dependency on javax.sip.SipException. Change-Id: I77d289bef1b5e7f1ec0c0408d0bbf96c21085cd7 --- java/android/net/sip/SipAudioCall.java | 2 -- java/android/net/sip/SipAudioCallImpl.java | 1 - java/android/net/sip/SipException.java | 37 ++++++++++++++++++++++++++++++ java/android/net/sip/SipManager.java | 7 +++--- 4 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 java/android/net/sip/SipException.java diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 02f82b3..39083a5 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -20,8 +20,6 @@ import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.os.Message; -import javax.sip.SipException; - /** * Interface for making audio calls over SIP. * @hide diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 2e2ca5b..67ba97f 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -43,7 +43,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sdp.SdpException; -import javax.sip.SipException; /** * Class that handles an audio call over SIP. diff --git a/java/android/net/sip/SipException.java b/java/android/net/sip/SipException.java new file mode 100644 index 0000000..d615342 --- /dev/null +++ b/java/android/net/sip/SipException.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * @hide + */ +public class SipException extends Exception { + public SipException() { + } + + public SipException(String message) { + super(message); + } + + public SipException(String message, Throwable cause) { + // we want to eliminate the dependency on javax.sip.SipException + super(message, ((cause instanceof javax.sip.SipException) + && (cause.getCause() != null)) + ? cause.getCause() + : cause); + } +} diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index beec8fe..ccae7f9 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -25,7 +25,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import java.text.ParseException; -import javax.sip.SipException; /** * The class provides API for various SIP related tasks. Specifically, the API @@ -501,15 +500,15 @@ public class SipManager { } @Override - public void onRegistrationFailed(ISipSession session, String className, + public void onRegistrationFailed(ISipSession session, String errorCode, String message) { - mListener.onRegistrationFailed(getUri(session), className, message); + mListener.onRegistrationFailed(getUri(session), errorCode, message); } @Override public void onRegistrationTimeout(ISipSession session) { mListener.onRegistrationFailed(getUri(session), - SipException.class.getName(), "registration timed out"); + SipErrorCode.TIME_OUT.toString(), "registration timed out"); } } } -- cgit v1.2.3 From f18f1d352ec89b2765afe6b78508cf7d5fe6b996 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sun, 12 Sep 2010 23:50:38 +0800 Subject: SIP: enhance timeout and registration status feedback. http://b/issue?id=2984419 http://b/issue?id=2991065 Change-Id: I2d3b1dd3a70079ff347f7256f4684aea07847f4e --- java/android/net/sip/SipErrorCode.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index 2eb67e8..8624811 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -31,6 +31,9 @@ public enum SipErrorCode { /** When server responds with an error. */ SERVER_ERROR, + /** When transaction is terminated unexpectedly. */ + TRANSACTION_TERMINTED, + /** When some error occurs on the device, possibly due to a bug. */ CLIENT_ERROR, @@ -41,5 +44,8 @@ public enum SipErrorCode { INVALID_REMOTE_URI, /** When invalid credentials are provided. */ - INVALID_CREDENTIALS; + INVALID_CREDENTIALS, + + /** The client is in a transaction and cannot initiate a new one. */ + IN_PROGRESS; } -- cgit v1.2.3 From 614f7878d49baf7302e5d48bd9df9c26ef5cd6be Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Sep 2010 00:17:51 +0800 Subject: SipService: deliver connectivity change to all sessions. + add DATA_CONNECTION_LOST to SipErrorCode + convert it to Connection.DisconnectCause.LOST_SIGNAL in SipPhone http://b/issue?id=2992548 Change-Id: Ie8983c1b81077b21f46304cf60b8e61df1ffd241 --- java/android/net/sip/SipErrorCode.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index 8624811..963733e 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -47,5 +47,8 @@ public enum SipErrorCode { INVALID_CREDENTIALS, /** The client is in a transaction and cannot initiate a new one. */ - IN_PROGRESS; + IN_PROGRESS, + + /** When data connection is lost. */ + DATA_CONNECTION_LOST; } -- cgit v1.2.3 From 1ab079168ccc185408a8691c6b804021d79f7376 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Sep 2010 20:12:59 +0800 Subject: SIP: remove dependency on javax.sip and change errorCodeString to errorCode in SipRegistrationListener.onRegistrationFailed(). Change-Id: Id9618f5a4b0effaed04f8b0dc60347499d9e4501 --- java/android/net/sip/ISipSession.aidl | 1 - java/android/net/sip/SipManager.java | 5 +++-- java/android/net/sip/SipProfile.java | 18 +++++++++++------- java/android/net/sip/SipRegistrationListener.java | 4 ++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl index 1a23527..29fcb32 100644 --- a/java/android/net/sip/ISipSession.aidl +++ b/java/android/net/sip/ISipSession.aidl @@ -17,7 +17,6 @@ package android.net.sip; import android.net.sip.ISipSessionListener; -import android.net.sip.SessionDescription; import android.net.sip.SipProfile; /** diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index ccae7f9..149053c 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -502,13 +502,14 @@ public class SipManager { @Override public void onRegistrationFailed(ISipSession session, String errorCode, String message) { - mListener.onRegistrationFailed(getUri(session), errorCode, message); + mListener.onRegistrationFailed(getUri(session), + Enum.valueOf(SipErrorCode.class, errorCode), message); } @Override public void onRegistrationTimeout(ISipSession session) { mListener.onRegistrationFailed(getUri(session), - SipErrorCode.TIME_OUT.toString(), "registration timed out"); + SipErrorCode.TIME_OUT, "registration timed out"); } } } diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index aa2da75..1a7d8bf 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -167,11 +167,15 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * * @param port port number of the server * @return this builder object - * @throws InvalidArgumentException if the port number is out of range + * @throws IllegalArgumentException if the port number is out of range */ - public Builder setPort(int port) throws InvalidArgumentException { - mUri.setPort(port); - return this; + public Builder setPort(int port) throws IllegalArgumentException { + try { + mUri.setPort(port); + return this; + } catch (InvalidArgumentException e) { + throw new IllegalArgumentException(e); + } } /** @@ -180,16 +184,16 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * * @param protocol the protocol string * @return this builder object - * @throws InvalidArgumentException if the protocol is not recognized + * @throws IllegalArgumentException if the protocol is not recognized */ public Builder setProtocol(String protocol) - throws InvalidArgumentException { + throws IllegalArgumentException { if (protocol == null) { throw new NullPointerException("protocol cannot be null"); } protocol = protocol.toUpperCase(); if (!protocol.equals("UDP") && !protocol.equals("TCP")) { - throw new InvalidArgumentException( + throw new IllegalArgumentException( "unsupported protocol: " + protocol); } mProfile.mProtocol = protocol; diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index 22488d7..e751e46 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -40,9 +40,9 @@ public interface SipRegistrationListener { * Called when the registration fails. * * @param localProfileUri the URI string of the SIP profile to register with - * @param errorCode error code defined in {@link SipErrorCode} + * @param errorCode error code of this error * @param errorMessage error message */ - void onRegistrationFailed(String localProfileUri, String errorCode, + void onRegistrationFailed(String localProfileUri, SipErrorCode errorCode, String errorMessage); } -- cgit v1.2.3 From 188f70fdaf7d8cd20e9386051d06ca5a3ba0b683 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Sep 2010 21:28:12 +0800 Subject: SipAudioCall: use SipErrorCode instead of string in onError() and fix callback in setListener(). Change-Id: Ic2622df992a2ad45cb1e3f71736f320897ae8fb3 --- java/android/net/sip/SipAudioCall.java | 7 +-- java/android/net/sip/SipAudioCallImpl.java | 69 +++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 39083a5..1d1be69 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -88,10 +88,11 @@ public interface SipAudioCall { * Called when an error occurs. * * @param call the call object that carries out the audio call - * @param errorCode error code defined in {@link SipErrorCode} + * @param errorCode error code of this error * @param errorMessage error message */ - void onError(SipAudioCall call, String errorCode, String errorMessage); + void onError(SipAudioCall call, SipErrorCode errorCode, + String errorMessage); } /** @@ -125,7 +126,7 @@ public interface SipAudioCall { public void onCallHeld(SipAudioCall call) { onChanged(call); } - public void onError(SipAudioCall call, String errorCode, + public void onError(SipAudioCall call, SipErrorCode errorCode, String errorMessage) { onChanged(call); } diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 67ba97f..484eb1e 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -79,6 +79,9 @@ public class SipAudioCallImpl extends SipSessionAdapter private WifiManager mWm; private WifiManager.WifiLock mWifiHighPerfLock; + private SipErrorCode mErrorCode; + private String mErrorMessage; + public SipAudioCallImpl(Context context, SipProfile localProfile) { mContext = context; mLocalProfile = localProfile; @@ -92,23 +95,33 @@ public class SipAudioCallImpl extends SipSessionAdapter public void setListener(SipAudioCall.Listener listener, boolean callbackImmediately) { mListener = listener; - if ((listener == null) || !callbackImmediately) return; try { - SipSessionState state = getState(); - switch (state) { - case READY_TO_CALL: - listener.onReadyToCall(this); - break; - case INCOMING_CALL: - listener.onRinging(this, getPeerProfile(mSipSession)); - startRinging(); - break; - case OUTGOING_CALL: - listener.onCalling(this); - break; - default: - listener.onError(this, SipErrorCode.CLIENT_ERROR.toString(), - "wrong state to attach call: " + state); + if ((listener == null) || !callbackImmediately) { + // do nothing + } else if (mErrorCode != null) { + listener.onError(this, mErrorCode, mErrorMessage); + } else if (mInCall) { + if (mHold) { + listener.onCallHeld(this); + } else { + listener.onCallEstablished(this); + } + } else { + SipSessionState state = getState(); + switch (state) { + case READY_TO_CALL: + listener.onReadyToCall(this); + break; + case INCOMING_CALL: + listener.onRinging(this, getPeerProfile(mSipSession)); + break; + case OUTGOING_CALL: + listener.onCalling(this); + break; + case OUTGOING_CALL_RING_BACK: + listener.onRingingBack(this); + break; + } } } catch (Throwable t) { Log.e(TAG, "setListener()", t); @@ -135,6 +148,8 @@ public class SipAudioCallImpl extends SipSessionAdapter mInCall = false; mHold = false; mSessionId = -1L; + mErrorCode = null; + mErrorMessage = null; } public synchronized SipProfile getLocalProfile() { @@ -274,14 +289,20 @@ public class SipAudioCallImpl extends SipSessionAdapter } } + private SipErrorCode getErrorCode(String errorCode) { + return Enum.valueOf(SipErrorCode.class, errorCode); + } + @Override public void onCallChangeFailed(ISipSession session, String errorCode, String message) { Log.d(TAG, "sip call change failed: " + message); + mErrorCode = getErrorCode(errorCode); + mErrorMessage = message; Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, errorCode, message); + listener.onError(SipAudioCallImpl.this, mErrorCode, message); } catch (Throwable t) { Log.e(TAG, "onCallBusy()", t); } @@ -289,16 +310,20 @@ public class SipAudioCallImpl extends SipSessionAdapter } @Override - public void onError(ISipSession session, String errorCode, - String message) { + public void onError(ISipSession session, String errorCode, String message) { Log.d(TAG, "sip session error: " + errorCode + ": " + message); + mErrorCode = getErrorCode(errorCode); + mErrorMessage = message; synchronized (this) { - if (!isInCall()) close(true); + if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST) + || !isInCall()) { + close(true); + } } Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, errorCode, message); + listener.onError(SipAudioCallImpl.this, mErrorCode, message); } catch (Throwable t) { Log.e(TAG, "onError()", t); } @@ -311,6 +336,8 @@ public class SipAudioCallImpl extends SipSessionAdapter try { mPeerSd = new SdpSessionDescription(sessionDescription); session.setListener(this); + + if (getState() == SipSessionState.INCOMING_CALL) startRinging(); } catch (Throwable e) { Log.e(TAG, "attachCall()", e); throwSipException(e); -- cgit v1.2.3 From 72e725a66e388eca197a1e3ee118bc6481a82fe4 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Sep 2010 19:33:10 +0800 Subject: SipService: ignore connect event for non-active networks. + sanity check and remove redundant code. Change-Id: I4d3e226851ad7fc4d88ddcd0a5c58f7e33b6c14a --- java/android/net/sip/SipAudioCallImpl.java | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 484eb1e..8bf486a 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -184,7 +184,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onCalling(SipAudioCallImpl.this); + listener.onCalling(this); } catch (Throwable t) { Log.e(TAG, "onCalling()", t); } @@ -198,7 +198,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onRingingBack(SipAudioCallImpl.this); + listener.onRingingBack(this); } catch (Throwable t) { Log.e(TAG, "onRingingBack()", t); } @@ -251,9 +251,9 @@ public class SipAudioCallImpl extends SipSessionAdapter if (listener != null) { try { if (mHold) { - listener.onCallHeld(SipAudioCallImpl.this); + listener.onCallHeld(this); } else { - listener.onCallEstablished(SipAudioCallImpl.this); + listener.onCallEstablished(this); } } catch (Throwable t) { Log.e(TAG, "onCallEstablished()", t); @@ -268,7 +268,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onCallEnded(SipAudioCallImpl.this); + listener.onCallEnded(this); } catch (Throwable t) { Log.e(TAG, "onCallEnded()", t); } @@ -282,7 +282,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onCallBusy(SipAudioCallImpl.this); + listener.onCallBusy(this); } catch (Throwable t) { Log.e(TAG, "onCallBusy()", t); } @@ -302,7 +302,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, mErrorCode, message); + listener.onError(this, mErrorCode, message); } catch (Throwable t) { Log.e(TAG, "onCallBusy()", t); } @@ -310,9 +310,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } @Override - public void onError(ISipSession session, String errorCode, String message) { - Log.d(TAG, "sip session error: " + errorCode + ": " + message); - mErrorCode = getErrorCode(errorCode); + public void onError(ISipSession session, String errorCodeString, + String message) { + Log.d(TAG, "sip session error: " + errorCodeString + ": " + message); + SipErrorCode errorCode = mErrorCode = getErrorCode(errorCodeString); mErrorMessage = message; synchronized (this) { if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST) @@ -323,7 +324,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Listener listener = mListener; if (listener != null) { try { - listener.onError(SipAudioCallImpl.this, mErrorCode, message); + listener.onError(this, errorCode, message); } catch (Throwable t) { Log.e(TAG, "onError()", t); } -- cgit v1.2.3 From 1c77ba42254cbe84cc1c1a2c31ef7839c2294999 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 15 Sep 2010 11:23:22 +0800 Subject: SIP: add PEER_NOT_REACHABLE error feedback. http://b/issue?id=3002033 Change-Id: Ib64b08919d214acbab89945ac19dc113a68e62ad --- java/android/net/sip/SipErrorCode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index 963733e..ee985de 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -43,6 +43,9 @@ public enum SipErrorCode { /** When the remote URI is not valid. */ INVALID_REMOTE_URI, + /** When the peer is not reachable. */ + PEER_NOT_REACHABLE, + /** When invalid credentials are provided. */ INVALID_CREDENTIALS, -- cgit v1.2.3 From 901503e1cf8b6cfac1540a22c325eb5cdd879429 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Sep 2010 20:43:54 +0800 Subject: Fix links in SIP API javadoc. Change-Id: I839280fe18502bb576f6e9c9a7948077c02fa570 --- java/android/net/sip/ISipSession.aidl | 4 +- java/android/net/sip/SipAudioCall.java | 12 +++--- java/android/net/sip/SipErrorCode.java | 8 ++-- java/android/net/sip/SipException.java | 1 + java/android/net/sip/SipManager.java | 46 ++++++++++------------- java/android/net/sip/SipProfile.java | 8 ++-- java/android/net/sip/SipRegistrationListener.java | 6 +-- 7 files changed, 40 insertions(+), 45 deletions(-) diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl index 29fcb32..cd8bd2c 100644 --- a/java/android/net/sip/ISipSession.aidl +++ b/java/android/net/sip/ISipSession.aidl @@ -20,8 +20,8 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipProfile; /** - * A SIP session that is associated with a SIP dialog or a transaction - * (e.g., registration) not within a dialog. + * A SIP session that is associated with a SIP dialog or a transaction that is + * not within a dialog. * @hide */ interface ISipSession { diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 1d1be69..573760e 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -96,8 +96,8 @@ public interface SipAudioCall { } /** - * The adapter class for {@link SipAudioCall#Listener}. The default - * implementation of all callback methods is no-op. + * The adapter class for {@link Listener}. The default implementation of + * all callback methods is no-op. */ public class Adapter implements Listener { protected void onChanged(SipAudioCall call) { @@ -134,7 +134,7 @@ public interface SipAudioCall { /** * Sets the listener to listen to the audio call events. The method calls - * {@link #setListener(Listener, false)}. + * {@code setListener(listener, false)}. * * @param listener to listen to the audio call events of this object * @see #setListener(Listener, boolean) @@ -178,8 +178,8 @@ public interface SipAudioCall { void endCall() throws SipException; /** - * Puts a call on hold. When succeeds, - * {@link #Listener#onCallHeld(SipAudioCall)} is called. + * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is + * called. */ void holdCall() throws SipException; @@ -188,7 +188,7 @@ public interface SipAudioCall { /** * Continues a call that's on hold. When succeeds, - * {@link #Listener#onCallEstablished(SipAudioCall)} is called. + * {@link Listener#onCallEstablished} is called. */ void continueCall() throws SipException; diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index ee985de..a27f740 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -18,10 +18,10 @@ package android.net.sip; /** * Defines error code returned in - * {@link SipRegistrationListener#onRegistrationFailed(String, String, String)}, - * {@link ISipSessionListener#onError(ISipSession, String, String)}, - * {@link ISipSessionListener#onCallChangeFailed(ISipSession, String, String)} and - * {@link ISipSessionListener#onRegistrationFailed(ISipSession, String, String)}. + * {@link SipRegistrationListener#onRegistrationFailed}, + * {@link ISipSessionListener#onError}, + * {@link ISipSessionListener#onCallChangeFailed} and + * {@link ISipSessionListener#onRegistrationFailed}. * @hide */ public enum SipErrorCode { diff --git a/java/android/net/sip/SipException.java b/java/android/net/sip/SipException.java index d615342..f0d846b 100644 --- a/java/android/net/sip/SipException.java +++ b/java/android/net/sip/SipException.java @@ -17,6 +17,7 @@ package android.net.sip; /** + * General SIP-related exception class. * @hide */ public class SipException extends Exception { diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 149053c..5b1767d 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -28,25 +28,22 @@ import java.text.ParseException; /** * The class provides API for various SIP related tasks. Specifically, the API - * allows the application to: + * allows an application to: *
    *
  • register a {@link SipProfile} to 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(String)}, - * {@link #isOpened(String)} and {@link isRegistered(String)}. It also - * facilitates handling of the incoming call broadcast intent. See - * {@link #isIncomingCallIntent(Intent)}, {@link #getCallId(Intent)}, - * {@link #getOfferSessionDescription(Intent)} and - * {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}.
  • + * {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and + * {@link #isRegistered}. It also facilitates handling of the incoming call + * broadcast intent. See + * {@link #isIncomingCallIntent}, {@link #getCallId}, + * {@link #getOfferSessionDescription} and {@link #takeAudioCall}. *
  • make/take SIP-based audio calls. See - * {@link #makeAudioCall(Context, SipProfile, SipProfile, SipAudioCall.Listener)} - * and {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener}.
  • + * {@link #makeAudioCall} and {@link #takeAudioCall}. *
  • register/unregister with a SIP service provider. See - * {@link #register(SipProfile, int, ISipSessionListener)} and - * {@link #unregister(SipProfile, ISipSessionListener)}.
  • + * {@link #register} and {@link #unregister}. *
  • process SIP events directly with a {@link ISipSession} created by - * {@link createSipSession(SipProfile, ISipSessionListener)}.
  • + * {@link #createSipSession}. *
* @hide */ @@ -113,8 +110,7 @@ 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(Context, String, String, SipAudioCall.Listener)}. + * 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. @@ -134,8 +130,7 @@ 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(Context, String, String, SipAudioCall.Listener)}. + * 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. @@ -160,9 +155,7 @@ public class SipManager { /** * Sets the listener to listen to registration events. No effect if the - * profile has not been opened to receive calls - * (see {@link #open(SipProfile, String, SipRegistrationListener)} and - * {@link #open(SipProfile)}). + * profile has not been opened to receive calls (see {@link #open}). * * @param localProfileUri the URI of the profile * @param listener to listen to registration events; can be null @@ -283,7 +276,7 @@ public class SipManager { /** * Creates a {@link SipAudioCall} to take an incoming call. Before the call * is returned, the listener will receive a - * {@link SipAudioCall#Listener.onRinging(SipAudioCall, SipProfile)} + * {@link SipAudioCall.Listener#onRinging} * callback. * * @param context context to create a {@link SipAudioCall} object @@ -377,12 +370,11 @@ public class SipManager { /** * Registers the profile to the corresponding server 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. + * {@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. * * @param localProfile the SIP profile to register with - * @param expiryTime registration expiration time (in second) + * @param expiryTime registration expiration time (in seconds) * @param listener to listen to the registration events * @throws SipException if calling the SIP service results in an error */ @@ -419,9 +411,9 @@ public class SipManager { /** * Gets the {@link ISipSession} that handles the incoming call. For audio * calls, consider to use {@link SipAudioCall} to handle the incoming call. - * See {@link #takeAudioCall(Context, Intent, SipAudioCall.Listener)}. - * Note that the method may be called only once for the same intent. For - * subsequent calls on the same intent, the method returns null. + * See {@link #takeAudioCall}. Note that the method may be called only once + * for the same intent. For subsequent calls on the same intent, the method + * returns null. * * @param incomingCallIntent the incoming call broadcast intent * @return the session object that handles the incoming call diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 1a7d8bf..88bfba9 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -61,7 +61,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { }; /** - * Class to help create a {@link SipProfile}. + * Class to help create a {@code SipProfile}. */ public static class Builder { private AddressFactory mAddressFactory; @@ -120,8 +120,8 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * * @param username username of the SIP account * @param serverDomain the SIP server domain; if the network address - * is different from the domain, use - * {@link #setOutboundProxy(String)} to set server address + * is different from the domain, use {@link #setOutboundProxy} to + * set server address * @throws ParseException if the parameters are not valid */ public Builder(String username, String serverDomain) @@ -309,6 +309,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * Gets the SIP URI of this profile. * * @return the SIP URI of this profile + * @hide */ public SipURI getUri() { return (SipURI) mAddress.getURI(); @@ -327,6 +328,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * Gets the SIP address of this profile. * * @return the SIP address of this profile + * @hide */ public Address getSipAddress() { return mAddress; diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index e751e46..705f271 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -29,15 +29,15 @@ public interface SipRegistrationListener { void onRegistering(String localProfileUri); /** - * Called when registration is successfully done. + * Called when the registration succeeded. * * @param localProfileUri the URI string of the SIP profile to register with - * @param expiryTime duration in second before the registration expires + * @param expiryTime duration in seconds before the registration expires */ void onRegistrationDone(String localProfileUri, long expiryTime); /** - * Called when the registration fails. + * Called when the registration failed. * * @param localProfileUri the URI string of the SIP profile to register with * @param errorCode error code of this error -- cgit v1.2.3 From f498e98d2e2910e866ec0728cebbe676dd475d9e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Sep 2010 20:14:18 +0800 Subject: Add timer to SIP session creation process. + add timer parameter to ISipSession.make/changeCall(), + add timer paramter to SipAudioCall.make/answer/hold/continueCall()'s, + add timer parameter to SipManager.makeAudioCall(), + modify implementation in SipSessionGroup, SipAudioCallImpl accordingly, + make SipPhone to use it with 8-second timeout. http://b/issue?id=2994748 Change-Id: I661a887e5810087ddc5e2318335e2fa427f80ec6 --- java/android/net/sip/ISipSession.aidl | 12 ++++++--- java/android/net/sip/SipAudioCall.java | 42 ++++++++++++++++++++++++------ java/android/net/sip/SipAudioCallImpl.java | 31 +++++++++++++++------- java/android/net/sip/SipManager.java | 23 +++++++++++----- 4 files changed, 81 insertions(+), 27 deletions(-) diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl index cd8bd2c..5661b8f 100644 --- a/java/android/net/sip/ISipSession.aidl +++ b/java/android/net/sip/ISipSession.aidl @@ -112,9 +112,11 @@ interface ISipSession { * * @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 * @see ISipSessionListener */ - void makeCall(in SipProfile callee, String sessionDescription); + void makeCall(in SipProfile callee, String sessionDescription, int timeout); /** * Answers an incoming call with the specified session description. The @@ -122,8 +124,10 @@ interface ISipSession { * {@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 */ - void answerCall(String sessionDescription); + void answerCall(String sessionDescription, int timeout); /** * Ends an established call, terminates an outgoing call or rejects an @@ -140,6 +144,8 @@ interface ISipSession { * 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 */ - void changeCall(String sessionDescription); + void changeCall(String sessionDescription, int timeout); } diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 573760e..2a9a65b 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -158,12 +158,18 @@ public interface SipAudioCall { void close(); /** - * Initiates an audio call to the specified profile. + * 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 + * @see Listener.onError */ - void makeCall(SipProfile callee, SipManager sipManager) throws SipException; + void makeCall(SipProfile callee, SipManager sipManager, int timeout) + throws SipException; /** * Attaches an incoming call to this call object. @@ -179,18 +185,38 @@ public interface SipAudioCall { /** * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is - * called. + * called. 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 timeout the timeout value in seconds + * @see Listener.onError */ - void holdCall() throws SipException; + void holdCall(int timeout) throws SipException; - /** Answers a call. */ - void answerCall() throws SipException; + /** + * Answers a call. 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 timeout the timeout value in seconds + * @see Listener.onError + */ + void answerCall(int timeout) throws SipException; /** * Continues a call that's on hold. When succeeds, - * {@link Listener#onCallEstablished} is called. + * {@link Listener#onCallEstablished} is called. 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 timeout the timeout value in seconds + * @see Listener.onError */ - void continueCall() throws SipException; + void continueCall(int timeout) throws SipException; /** Puts the device to speaker mode. */ void setSpeakerMode(boolean speakerMode); diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 8bf486a..fcabcc4 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -55,6 +55,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private static final boolean DONT_RELEASE_SOCKET = false; private static final String AUDIO = "audio"; private static final int DTMF = 101; + private static final int SESSION_TIMEOUT = 5; // in seconds private Context mContext; private SipProfile mLocalProfile; @@ -144,12 +145,21 @@ public class SipAudioCallImpl extends SipSessionAdapter if (closeRtp) stopCall(RELEASE_SOCKET); stopRingbackTone(); stopRinging(); - mSipSession = null; + mInCall = false; mHold = false; mSessionId = -1L; mErrorCode = null; mErrorMessage = null; + + if (mSipSession != null) { + try { + mSipSession.setListener(null); + } catch (RemoteException e) { + // don't care + } + mSipSession = null; + } } public synchronized SipProfile getLocalProfile() { @@ -219,7 +229,7 @@ public class SipAudioCallImpl extends SipSessionAdapter // session changing request try { mPeerSd = new SdpSessionDescription(sessionDescription); - answerCall(); + answerCall(SESSION_TIMEOUT); } catch (Throwable e) { Log.e(TAG, "onRinging()", e); session.endCall(); @@ -346,14 +356,15 @@ public class SipAudioCallImpl extends SipSessionAdapter } public synchronized void makeCall(SipProfile peerProfile, - SipManager sipManager) throws SipException { + SipManager sipManager, int timeout) throws SipException { try { mSipSession = sipManager.createSipSession(mLocalProfile, this); if (mSipSession == null) { throw new SipException( "Failed to create SipSession; network available?"); } - mSipSession.makeCall(peerProfile, createOfferSessionDescription()); + mSipSession.makeCall(peerProfile, createOfferSessionDescription(), + timeout); } catch (Throwable e) { if (e instanceof SipException) { throw (SipException) e; @@ -376,10 +387,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - public synchronized void holdCall() throws SipException { + public synchronized void holdCall(int timeout) throws SipException { if (mHold) return; try { - mSipSession.changeCall(createHoldSessionDescription()); + mSipSession.changeCall(createHoldSessionDescription(), timeout); mHold = true; } catch (Throwable e) { throwSipException(e); @@ -389,21 +400,21 @@ public class SipAudioCallImpl extends SipSessionAdapter if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); } - public synchronized void answerCall() throws SipException { + public synchronized void answerCall(int timeout) throws SipException { try { stopRinging(); - mSipSession.answerCall(createAnswerSessionDescription()); + mSipSession.answerCall(createAnswerSessionDescription(), timeout); } catch (Throwable e) { Log.e(TAG, "answerCall()", e); throwSipException(e); } } - public synchronized void continueCall() throws SipException { + public synchronized void continueCall(int timeout) throws SipException { if (!mHold) return; try { mHold = false; - mSipSession.changeCall(createContinueSessionDescription()); + mSipSession.changeCall(createContinueSessionDescription(), timeout); } catch (Throwable e) { throwSipException(e); } diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 5b1767d..36895cd 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -218,44 +218,55 @@ public class SipManager { } /** - * Creates a {@link SipAudioCall} to make a call. + * Creates a {@link SipAudioCall} to make a call. The attempt will be timed + * out if the call is not established within {@code timeout} seconds and + * {@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}; * can be null + * @param timeout the timeout value in seconds * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error + * @see SipAudioCall.Listener.onError */ public SipAudioCall makeAudioCall(Context context, SipProfile localProfile, - SipProfile peerProfile, SipAudioCall.Listener listener) + SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) throws SipException { SipAudioCall call = new SipAudioCallImpl(context, localProfile); call.setListener(listener); - call.makeCall(peerProfile, this); + call.makeCall(peerProfile, this, timeout); return call; } /** * Creates a {@link SipAudioCall} to make a call. To use this method, one - * must call {@link #open(SipProfile)} first. + * must call {@link #open(SipProfile)} first. The attempt will be timed out + * if the call is not established within {@code timeout} seconds and + * {@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}; * can be null + * @param timeout the timeout value in seconds * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error + * @see SipAudioCall.Listener.onError */ public SipAudioCall makeAudioCall(Context context, String localProfileUri, - String peerProfileUri, SipAudioCall.Listener listener) + String peerProfileUri, SipAudioCall.Listener listener, int timeout) throws SipException { try { return makeAudioCall(context, new SipProfile.Builder(localProfileUri).build(), - new SipProfile.Builder(peerProfileUri).build(), listener); + new SipProfile.Builder(peerProfileUri).build(), listener, + timeout); } catch (ParseException e) { throw new SipException("build SipProfile", e); } -- cgit v1.2.3 From ecd6e06707e7ce1001a05e919063da1c4b10d746 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 17 Sep 2010 15:40:31 +0800 Subject: SipAudioCall: expose startAudio() so that apps can start audio when time is right. Change-Id: I7ae96689d3a8006b34097533bc2434bc3814b82a --- java/android/net/sip/SipAudioCall.java | 8 +- java/android/net/sip/SipAudioCallImpl.java | 125 +++++++++++++++-------------- 2 files changed, 72 insertions(+), 61 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 2a9a65b..4abea20 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -153,7 +153,7 @@ public interface SipAudioCall { void setListener(Listener listener, boolean callbackImmediately); /** - * Closes this object. The object is not usable after being closed. + * Closes this object. This object is not usable after being closed. */ void close(); @@ -171,6 +171,12 @@ public interface SipAudioCall { void makeCall(SipProfile callee, SipManager sipManager, int timeout) throws SipException; + /** + * Starts the audio for the established call. This method should be called + * after {@link Listener#onCallEstablished} is called. + */ + void startAudio(); + /** * Attaches an incoming call to this call object. * diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index fcabcc4..e61e878 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -37,6 +37,7 @@ import android.util.Log; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; @@ -239,24 +240,18 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - private synchronized void establishCall(String sessionDescription) { + @Override + public void onCallEstablished(ISipSession session, + String sessionDescription) { stopRingbackTone(); stopRinging(); try { - SdpSessionDescription sd = - new SdpSessionDescription(sessionDescription); - Log.d(TAG, "sip call established: " + sd); - startCall(sd); - mInCall = true; + mPeerSd = new SdpSessionDescription(sessionDescription); + Log.d(TAG, "sip call established: " + mPeerSd); } catch (SdpException e) { Log.e(TAG, "createSessionDescription()", e); } - } - @Override - public void onCallEstablished(ISipSession session, - String sessionDescription) { - establishCall(sessionDescription); Listener listener = mListener; if (listener != null) { try { @@ -609,10 +604,23 @@ public class SipAudioCallImpl extends SipSessionAdapter return copies; } - private void startCall(SdpSessionDescription peerSd) { + public void startAudio() { + try { + startAudioInternal(); + } catch (UnknownHostException e) { + onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE.toString(), + e.getMessage()); + } catch (Throwable e) { + onError(mSipSession, SipErrorCode.CLIENT_ERROR.toString(), + e.getMessage()); + } + } + + private synchronized void startAudioInternal() throws UnknownHostException { stopCall(DONT_RELEASE_SOCKET); + mInCall = true; + SdpSessionDescription peerSd = mPeerSd; if (isWifiOn()) grabWifiHighPerfLock(); - mPeerSd = peerSd; String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); // TODO: handle multiple media fields int peerMediaPort = peerSd.getPeerMediaPort(AUDIO); @@ -621,58 +629,55 @@ public class SipAudioCallImpl extends SipSessionAdapter int localPort = getLocalMediaPort(); int sampleRate = 8000; int frameSize = sampleRate / 50; // 160 - try { - // TODO: get sample rate from sdp - mCodec = getCodec(peerSd); - - AudioStream audioStream = mAudioStream; - audioStream.associate(InetAddress.getByName(peerMediaAddress), - peerMediaPort); - audioStream.setCodec(convert(mCodec), mCodec.payloadType); - audioStream.setDtmfType(DTMF); - Log.d(TAG, "start media: localPort=" + localPort + ", peer=" - + peerMediaAddress + ":" + peerMediaPort); - - audioStream.setMode(RtpStream.MODE_NORMAL); - if (!mHold) { - // FIXME: won't work if peer is not sending nor receiving - if (!peerSd.isSending(AUDIO)) { - Log.d(TAG, " not receiving"); - audioStream.setMode(RtpStream.MODE_SEND_ONLY); - } - if (!peerSd.isReceiving(AUDIO)) { - Log.d(TAG, " not sending"); - audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); - } - /* 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); + // TODO: get sample rate from sdp + mCodec = getCodec(peerSd); + + AudioStream audioStream = mAudioStream; + audioStream.associate(InetAddress.getByName(peerMediaAddress), + peerMediaPort); + audioStream.setCodec(convert(mCodec), mCodec.payloadType); + audioStream.setDtmfType(DTMF); + Log.d(TAG, "start media: localPort=" + localPort + ", peer=" + + peerMediaAddress + ":" + peerMediaPort); + + audioStream.setMode(RtpStream.MODE_NORMAL); + if (!mHold) { + // FIXME: won't work if peer is not sending nor receiving + if (!peerSd.isSending(AUDIO)) { + Log.d(TAG, " not receiving"); + audioStream.setMode(RtpStream.MODE_SEND_ONLY); + } + if (!peerSd.isReceiving(AUDIO)) { + Log.d(TAG, " not sending"); + audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); } - // 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 + /* 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(); + audioStream.join(audioGroup); + if (mMuted) { + audioGroup.setMode(AudioGroup.MODE_MUTED); } else { - if (audioGroup == null) audioGroup = new AudioGroup(); - audioStream.join(audioGroup); - if (mMuted) { - audioGroup.setMode(AudioGroup.MODE_MUTED); - } else { - audioGroup.setMode(AudioGroup.MODE_NORMAL); - } + audioGroup.setMode(AudioGroup.MODE_NORMAL); } - } catch (Exception e) { - Log.e(TAG, "call()", e); } } -- cgit v1.2.3 From 50e8ac3c1990ea010fa92daa1731aac273b87017 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 17 Sep 2010 17:42:19 +0800 Subject: Fix build Change-Id: If2fdfcb6da3a1cf5f0d62ddfc0f2c98be55b32a7 --- java/android/net/sip/SipAudioCallImpl.java | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 1168217..e61e878 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -28,6 +28,7 @@ import android.net.rtp.AudioCodec; import android.net.rtp.AudioGroup; import android.net.rtp.AudioStream; import android.net.rtp.RtpStream; +import android.net.wifi.WifiManager; import android.os.Message; import android.os.RemoteException; import android.os.Vibrator; @@ -77,6 +78,8 @@ public class SipAudioCallImpl extends SipSessionAdapter private ToneGenerator mRingbackTone; private SipProfile mPendingCallRequest; + private WifiManager mWm; + private WifiManager.WifiLock mWifiHighPerfLock; private SipErrorCode mErrorCode; private String mErrorMessage; @@ -84,6 +87,7 @@ public class SipAudioCallImpl extends SipSessionAdapter public SipAudioCallImpl(Context context, SipProfile localProfile) { mContext = context; mLocalProfile = localProfile; + mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } public void setListener(SipAudioCall.Listener listener) { @@ -446,6 +450,28 @@ public class SipAudioCallImpl extends SipSessionAdapter } } + 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; + } + private String createContinueSessionDescription() { return createSdpBuilder(true, mCodec).build(); } @@ -657,6 +683,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private void stopCall(boolean releaseSocket) { Log.d(TAG, "stop audiocall"); + releaseWifiHighPerfLock(); if (mAudioStream != null) { mAudioStream.join(null); -- cgit v1.2.3 From 9b14c738a33ce4eebfef4c2fa03c5d8bea000de3 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 17 Sep 2010 18:23:26 +0800 Subject: Fix build. Change-Id: Icb6faa2ed6428d9db7f880a1855ce01d8cd22495 --- java/android/net/sip/SipAudioCallImpl.java | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index e61e878..fd32d4d 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -78,8 +78,6 @@ public class SipAudioCallImpl extends SipSessionAdapter private ToneGenerator mRingbackTone; private SipProfile mPendingCallRequest; - private WifiManager mWm; - private WifiManager.WifiLock mWifiHighPerfLock; private SipErrorCode mErrorCode; private String mErrorMessage; @@ -87,7 +85,6 @@ public class SipAudioCallImpl extends SipSessionAdapter public SipAudioCallImpl(Context context, SipProfile localProfile) { mContext = context; mLocalProfile = localProfile; - mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } public void setListener(SipAudioCall.Listener listener) { @@ -450,28 +447,6 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - 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; - } - private String createContinueSessionDescription() { return createSdpBuilder(true, mCodec).build(); } @@ -620,7 +595,6 @@ public class SipAudioCallImpl extends SipSessionAdapter stopCall(DONT_RELEASE_SOCKET); mInCall = true; SdpSessionDescription peerSd = mPeerSd; - if (isWifiOn()) grabWifiHighPerfLock(); String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); // TODO: handle multiple media fields int peerMediaPort = peerSd.getPeerMediaPort(AUDIO); @@ -683,7 +657,6 @@ public class SipAudioCallImpl extends SipSessionAdapter private void stopCall(boolean releaseSocket) { Log.d(TAG, "stop audiocall"); - releaseWifiHighPerfLock(); if (mAudioStream != null) { mAudioStream.join(null); -- cgit v1.2.3 From d96284491dfc918c72258f84b47a6554c21db92e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sun, 19 Sep 2010 18:23:44 +0800 Subject: SIP: add config flag for wifi-only configuration. http://b/issue?id=2994029 Change-Id: I328da9b0f8b70d660dbcefffdac8250341792101 --- java/android/net/sip/SipManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 36895cd..9ee6f34 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -97,6 +97,14 @@ public class SipManager { */ } + /** + * Returns true if SIP is only available on WIFI. + */ + public static boolean isSipWifiOnly(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_sip_wifi_only); + } + private SipManager() { createSipService(); } -- cgit v1.2.3 From a0171082cfc4b860a82dcf5ebbd498b253f1032f Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 17 Sep 2010 16:58:51 +0800 Subject: SIP: convert enum to static final int. Converts SipErrorCode and SipSessionState. Change-Id: Iee3a465649ea89d395b2336bbd673c25113e5f93 --- java/android/net/sip/ISipSession.aidl | 7 +-- java/android/net/sip/ISipSessionListener.aidl | 7 ++- java/android/net/sip/SipAudioCall.java | 9 ++-- java/android/net/sip/SipAudioCallImpl.java | 40 +++++++------- java/android/net/sip/SipErrorCode.java | 57 ++++++++++++++++---- java/android/net/sip/SipManager.java | 5 +- java/android/net/sip/SipRegistrationListener.java | 3 +- java/android/net/sip/SipSessionAdapter.java | 6 +-- java/android/net/sip/SipSessionState.java | 64 ++++++++++++++++------- 9 files changed, 127 insertions(+), 71 deletions(-) diff --git a/java/android/net/sip/ISipSession.aidl b/java/android/net/sip/ISipSession.aidl index 5661b8f..2d515db 100644 --- a/java/android/net/sip/ISipSession.aidl +++ b/java/android/net/sip/ISipSession.aidl @@ -49,14 +49,11 @@ interface ISipSession { /** * Gets the session state. The value returned must be one of the states in - * {@link SipSessionState}. One may convert it to {@link SipSessionState} by - * - * Enum.valueOf(SipSessionState.class, session.getState()); - * + * {@link SipSessionState}. * * @return the session state */ - String getState(); + int getState(); /** * Checks if the session is in a call. diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl index 0a6220b..5920bca 100644 --- a/java/android/net/sip/ISipSessionListener.aidl +++ b/java/android/net/sip/ISipSessionListener.aidl @@ -79,8 +79,7 @@ interface ISipSessionListener { * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onError(in ISipSession session, String errorCode, - String errorMessage); + void onError(in ISipSession session, int errorCode, String errorMessage); /** * Called when an error occurs during session modification negotiation. @@ -89,7 +88,7 @@ interface ISipSessionListener { * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onCallChangeFailed(in ISipSession session, String errorCode, + void onCallChangeFailed(in ISipSession session, int errorCode, String errorMessage); /** @@ -114,7 +113,7 @@ interface ISipSessionListener { * @param errorCode error code defined in {@link SipErrorCode} * @param errorMessage error message */ - void onRegistrationFailed(in ISipSession session, String errorCode, + void onRegistrationFailed(in ISipSession session, int errorCode, String errorMessage); /** diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 4abea20..0069fe0 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -90,9 +90,9 @@ public interface SipAudioCall { * @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, SipErrorCode errorCode, - String errorMessage); + void onError(SipAudioCall call, int errorCode, String errorMessage); } /** @@ -126,7 +126,7 @@ public interface SipAudioCall { public void onCallHeld(SipAudioCall call) { onChanged(call); } - public void onError(SipAudioCall call, SipErrorCode errorCode, + public void onError(SipAudioCall call, int errorCode, String errorMessage) { onChanged(call); } @@ -318,10 +318,11 @@ public interface SipAudioCall { /** * 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 */ - SipSessionState getState(); + int getState(); /** * Gets the {@link ISipSession} that carries this call. diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index e61e878..111bfff 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -81,7 +81,7 @@ public class SipAudioCallImpl extends SipSessionAdapter private WifiManager mWm; private WifiManager.WifiLock mWifiHighPerfLock; - private SipErrorCode mErrorCode; + private int mErrorCode = SipErrorCode.NO_ERROR; private String mErrorMessage; public SipAudioCallImpl(Context context, SipProfile localProfile) { @@ -100,7 +100,7 @@ public class SipAudioCallImpl extends SipSessionAdapter try { if ((listener == null) || !callbackImmediately) { // do nothing - } else if (mErrorCode != null) { + } else if (mErrorCode != SipErrorCode.NO_ERROR) { listener.onError(this, mErrorCode, mErrorMessage); } else if (mInCall) { if (mHold) { @@ -109,18 +109,18 @@ public class SipAudioCallImpl extends SipSessionAdapter listener.onCallEstablished(this); } } else { - SipSessionState state = getState(); + int state = getState(); switch (state) { - case READY_TO_CALL: + case SipSessionState.READY_TO_CALL: listener.onReadyToCall(this); break; - case INCOMING_CALL: + case SipSessionState.INCOMING_CALL: listener.onRinging(this, getPeerProfile(mSipSession)); break; - case OUTGOING_CALL: + case SipSessionState.OUTGOING_CALL: listener.onCalling(this); break; - case OUTGOING_CALL_RING_BACK: + case SipSessionState.OUTGOING_CALL_RING_BACK: listener.onRingingBack(this); break; } @@ -150,7 +150,7 @@ public class SipAudioCallImpl extends SipSessionAdapter mInCall = false; mHold = false; mSessionId = -1L; - mErrorCode = null; + mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; if (mSipSession != null) { @@ -175,10 +175,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - public synchronized SipSessionState getState() { + public synchronized int getState() { if (mSipSession == null) return SipSessionState.READY_TO_CALL; try { - return Enum.valueOf(SipSessionState.class, mSipSession.getState()); + return mSipSession.getState(); } catch (RemoteException e) { return SipSessionState.REMOTE_ERROR; } @@ -294,15 +294,11 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - private SipErrorCode getErrorCode(String errorCode) { - return Enum.valueOf(SipErrorCode.class, errorCode); - } - @Override - public void onCallChangeFailed(ISipSession session, String errorCode, + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { Log.d(TAG, "sip call change failed: " + message); - mErrorCode = getErrorCode(errorCode); + mErrorCode = errorCode; mErrorMessage = message; Listener listener = mListener; if (listener != null) { @@ -315,10 +311,10 @@ public class SipAudioCallImpl extends SipSessionAdapter } @Override - public void onError(ISipSession session, String errorCodeString, - String message) { - Log.d(TAG, "sip session error: " + errorCodeString + ": " + message); - SipErrorCode errorCode = mErrorCode = getErrorCode(errorCodeString); + public void onError(ISipSession session, int errorCode, String message) { + Log.d(TAG, "sip session error: " + SipErrorCode.toString(errorCode) + + ": " + message); + mErrorCode = errorCode; mErrorMessage = message; synchronized (this) { if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST) @@ -608,10 +604,10 @@ public class SipAudioCallImpl extends SipSessionAdapter try { startAudioInternal(); } catch (UnknownHostException e) { - onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE.toString(), + onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); } catch (Throwable e) { - onError(mSipSession, SipErrorCode.CLIENT_ERROR.toString(), + onError(mSipSession, SipErrorCode.CLIENT_ERROR, e.getMessage()); } } diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index a27f740..7496d28 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -24,34 +24,69 @@ package android.net.sip; * {@link ISipSessionListener#onRegistrationFailed}. * @hide */ -public enum SipErrorCode { +public class SipErrorCode { + /** Not an error. */ + public static final int NO_ERROR = 0; + /** When some socket error occurs. */ - SOCKET_ERROR, + public static final int SOCKET_ERROR = -1; /** When server responds with an error. */ - SERVER_ERROR, + public static final int SERVER_ERROR = -2; /** When transaction is terminated unexpectedly. */ - TRANSACTION_TERMINTED, + public static final int TRANSACTION_TERMINTED = -3; /** When some error occurs on the device, possibly due to a bug. */ - CLIENT_ERROR, + public static final int CLIENT_ERROR = -4; /** When the transaction gets timed out. */ - TIME_OUT, + public static final int TIME_OUT = -5; /** When the remote URI is not valid. */ - INVALID_REMOTE_URI, + public static final int INVALID_REMOTE_URI = -6; /** When the peer is not reachable. */ - PEER_NOT_REACHABLE, + public static final int PEER_NOT_REACHABLE = -7; /** When invalid credentials are provided. */ - INVALID_CREDENTIALS, + public static final int INVALID_CREDENTIALS = -8; /** The client is in a transaction and cannot initiate a new one. */ - IN_PROGRESS, + public static final int IN_PROGRESS = -9; /** When data connection is lost. */ - DATA_CONNECTION_LOST; + public static final int DATA_CONNECTION_LOST = -10; + + public static String toString(int errorCode) { + switch (errorCode) { + case NO_ERROR: + return "NO_ERROR"; + case SOCKET_ERROR: + return "SOCKET_ERROR"; + case SERVER_ERROR: + return "SERVER_ERROR"; + case TRANSACTION_TERMINTED: + return "TRANSACTION_TERMINTED"; + case CLIENT_ERROR: + return "CLIENT_ERROR"; + case TIME_OUT: + return "TIME_OUT"; + case INVALID_REMOTE_URI: + return "INVALID_REMOTE_URI"; + case PEER_NOT_REACHABLE: + return "PEER_NOT_REACHABLE"; + case INVALID_CREDENTIALS: + return "INVALID_CREDENTIALS"; + case IN_PROGRESS: + return "IN_PROGRESS"; + case DATA_CONNECTION_LOST: + return "DATA_CONNECTION_LOST"; + default: + return "UNKNOWN"; + } + } + + private SipErrorCode() { + } } diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 9ee6f34..31768d7 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -511,10 +511,9 @@ public class SipManager { } @Override - public void onRegistrationFailed(ISipSession session, String errorCode, + public void onRegistrationFailed(ISipSession session, int errorCode, String message) { - mListener.onRegistrationFailed(getUri(session), - Enum.valueOf(SipErrorCode.class, errorCode), message); + mListener.onRegistrationFailed(getUri(session), errorCode, message); } @Override diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index 705f271..37c9ce2 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -42,7 +42,8 @@ public interface SipRegistrationListener { * @param localProfileUri the URI string of the SIP profile to register with * @param errorCode error code of this error * @param errorMessage error message + * @see SipErrorCode */ - void onRegistrationFailed(String localProfileUri, SipErrorCode errorCode, + void onRegistrationFailed(String localProfileUri, int errorCode, String errorMessage); } diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java index 6020f2c..86aca37 100644 --- a/java/android/net/sip/SipSessionAdapter.java +++ b/java/android/net/sip/SipSessionAdapter.java @@ -42,11 +42,11 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onCallBusy(ISipSession session) { } - public void onCallChangeFailed(ISipSession session, String errorCode, + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { } - public void onError(ISipSession session, String errorCode, String message) { + public void onError(ISipSession session, int errorCode, String message) { } public void onRegistering(ISipSession session) { @@ -55,7 +55,7 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onRegistrationDone(ISipSession session, int duration) { } - public void onRegistrationFailed(ISipSession session, String errorCode, + public void onRegistrationFailed(ISipSession session, int errorCode, String message) { } diff --git a/java/android/net/sip/SipSessionState.java b/java/android/net/sip/SipSessionState.java index 5bab112..31e9d3f 100644 --- a/java/android/net/sip/SipSessionState.java +++ b/java/android/net/sip/SipSessionState.java @@ -20,47 +20,75 @@ package android.net.sip; * Defines {@link ISipSession} states. * @hide */ -public enum SipSessionState { +public class SipSessionState { /** When session is ready to initiate a call or transaction. */ - READY_TO_CALL, + public static final int READY_TO_CALL = 0; /** When the registration request is sent out. */ - REGISTERING, + public static final int REGISTERING = 1; /** When the unregistration request is sent out. */ - DEREGISTERING, + public static final int DEREGISTERING = 2; /** When an INVITE request is received. */ - INCOMING_CALL, + public static final int INCOMING_CALL = 3; /** When an OK response is sent for the INVITE request received. */ - INCOMING_CALL_ANSWERING, + public static final int INCOMING_CALL_ANSWERING = 4; /** When an INVITE request is sent. */ - OUTGOING_CALL, + public static final int OUTGOING_CALL = 5; /** When a RINGING response is received for the INVITE request sent. */ - OUTGOING_CALL_RING_BACK, + public static final int OUTGOING_CALL_RING_BACK = 6; /** When a CANCEL request is sent for the INVITE request sent. */ - OUTGOING_CALL_CANCELING, + public static final int OUTGOING_CALL_CANCELING = 7; /** When a call is established. */ - IN_CALL, + public static final int IN_CALL = 8; /** Some error occurs when making a remote call to {@link ISipSession}. */ - REMOTE_ERROR, + public static final int REMOTE_ERROR = 9; /** When an OPTIONS request is sent. */ - PINGING; + public static final int PINGING = 10; + + /** Not defined. */ + public static final int NOT_DEFINED = 101; /** - * Checks if the specified string represents the same state as this object. - * - * @return true if the specified string represents the same state as this - * object + * Converts the state to string. */ - public boolean equals(String state) { - return toString().equals(state); + 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() { } } -- cgit v1.2.3 From 9b71bf7bdf56645e58796ff0442766507b698b4b Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 20 Sep 2010 10:06:31 +0800 Subject: SipPhone: fix missing-call DisconnectCause feedback also fix delivering bad news before closing a SipAudioCallImpl object so that apps can get the current audio-call object state before it's closed: http://b/issue?id=3009262 Change-Id: I94c19dae8b4f252de869e614ec462b19b4ff2077 --- java/android/net/sip/SipAudioCallImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index 111bfff..ccf4d15 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -269,7 +269,6 @@ public class SipAudioCallImpl extends SipSessionAdapter @Override public void onCallEnded(ISipSession session) { Log.d(TAG, "sip call ended: " + session); - close(); Listener listener = mListener; if (listener != null) { try { @@ -278,12 +277,12 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.e(TAG, "onCallEnded()", t); } } + close(); } @Override public void onCallBusy(ISipSession session) { Log.d(TAG, "sip call busy: " + session); - close(false); Listener listener = mListener; if (listener != null) { try { @@ -292,6 +291,7 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.e(TAG, "onCallBusy()", t); } } + close(false); } @Override @@ -316,12 +316,6 @@ public class SipAudioCallImpl extends SipSessionAdapter + ": " + message); mErrorCode = errorCode; mErrorMessage = message; - synchronized (this) { - if ((mErrorCode == SipErrorCode.DATA_CONNECTION_LOST) - || !isInCall()) { - close(true); - } - } Listener listener = mListener; if (listener != null) { try { @@ -330,6 +324,12 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.e(TAG, "onError()", t); } } + synchronized (this) { + if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST) + || !isInCall()) { + close(true); + } + } } public synchronized void attachCall(ISipSession session, -- cgit v1.2.3 From 7f383063fafc54a41f91540a41bf987003fd2502 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 16 Sep 2010 09:56:38 +0800 Subject: RTP: Extend codec capability and update the APIs. Change-Id: I37ba9d83c2de3c5dae2bfc1b7513df7f6fee3c5c --- java/android/net/rtp/AudioCodec.java | 140 ++++++++++++++++++++++++++++------ java/android/net/rtp/AudioGroup.java | 91 +++++++++++++++++++--- java/android/net/rtp/AudioStream.java | 47 +++++++----- java/android/net/rtp/RtpStream.java | 28 +++++-- 4 files changed, 244 insertions(+), 62 deletions(-) diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java index 89e6aa9..4851a46 100644 --- a/java/android/net/rtp/AudioCodec.java +++ b/java/android/net/rtp/AudioCodec.java @@ -16,41 +16,133 @@ package android.net.rtp; -/** @hide */ +import java.util.Arrays; + +/** + * This class defines a collection of audio codecs to be used with + * {@link AudioStream}s. Their parameters are designed to be exchanged using + * Session Description Protocol (SDP). Most of the values listed here can be + * found in RFC 3551, while others are described in separated standards. + * + *

Few simple configurations are defined as public static instances for the + * convenience of direct uses. More complicated ones could be obtained using + * {@link #getCodec(int, String, String)}. For example, one can use the + * following snippet to create a mode-1-only AMR codec.

+ *
+ * AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1");
+ * 
+ * + * @see AudioStream + * @hide + */ public class AudioCodec { - public static final AudioCodec ULAW = new AudioCodec("PCMU", 8000, 160, 0); - public static final AudioCodec ALAW = new AudioCodec("PCMA", 8000, 160, 8); + /** + * The RTP payload type of the encoding. + */ + public final int type; + + /** + * The encoding parameters to be used in the corresponding SDP attribute. + */ + public final String rtpmap; + + /** + * The format parameters to be used in the corresponding SDP attribute. + */ + public final String fmtp; + + /** + * G.711 u-law audio codec. + */ + public static final AudioCodec PCMU = new AudioCodec(0, "PCMU/8000", null); + + /** + * G.711 a-law audio codec. + */ + public static final AudioCodec PCMA = new AudioCodec(8, "PCMA/8000", null); /** - * Returns system supported codecs. + * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or + * simply FR. */ - public static AudioCodec[] getSystemSupportedCodecs() { - return new AudioCodec[] {AudioCodec.ULAW, AudioCodec.ALAW}; + public static final AudioCodec GSM = new AudioCodec(3, "GSM/8000", null); + + /** + * GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or + * simply EFR. + */ + public static final AudioCodec GSM_EFR = new AudioCodec(96, "GSM-EFR/8000", null); + + /** + * Adaptive Multi-Rate narrowband audio codec, also known as AMR or AMR-NB. + * Currently CRC, robust sorting, and interleaving are not supported. See + * more details about these features in RFC 4867. + */ + public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); + + // TODO: add rest of the codecs when the native part is done. + private static final AudioCodec[] sCodecs = {PCMU, PCMA}; + + private AudioCodec(int type, String rtpmap, String fmtp) { + this.type = type; + this.rtpmap = rtpmap; + this.fmtp = fmtp; } /** - * Returns the codec instance if it is supported by the system. + * Returns system supported audio codecs. + */ + public static AudioCodec[] getCodecs() { + return Arrays.copyOf(sCodecs, sCodecs.length); + } + + /** + * Creates an AudioCodec according to the given configuration. * - * @param name name of the codec - * @return the matched codec or null if the codec name is not supported by - * the system + * @param type The payload type of the encoding defined in RTP/AVP. + * @param rtpmap The encoding parameters specified in the corresponding SDP + * attribute, or null if it is not available. + * @param fmtp The format parameters specified in the corresponding SDP + * attribute, or null if it is not available. + * @return The configured AudioCodec or {@code null} if it is not supported. */ - public static AudioCodec getSystemSupportedCodec(String name) { - for (AudioCodec codec : getSystemSupportedCodecs()) { - if (codec.name.equals(name)) return codec; + public static AudioCodec getCodec(int type, String rtpmap, String fmtp) { + if (type < 0 || type > 127) { + return null; } - return null; - } - public final String name; - public final int sampleRate; - public final int sampleCount; - public final int defaultType; + AudioCodec hint = null; + if (rtpmap != null) { + String clue = rtpmap.trim().toUpperCase(); + for (AudioCodec codec : sCodecs) { + if (clue.startsWith(codec.rtpmap)) { + String channels = clue.substring(codec.rtpmap.length()); + if (channels.length() == 0 || channels.equals("/1")) { + hint = codec; + } + break; + } + } + } else if (type < 96) { + for (AudioCodec codec : sCodecs) { + if (type == codec.type) { + hint = codec; + rtpmap = codec.rtpmap; + break; + } + } + } - private AudioCodec(String name, int sampleRate, int sampleCount, int defaultType) { - this.name = name; - this.sampleRate = sampleRate; - this.sampleCount = sampleCount; - this.defaultType = defaultType; + if (hint == null) { + return null; + } + if (hint == AMR && fmtp != null) { + String clue = fmtp.toLowerCase(); + if (clue.contains("crc=1") || clue.contains("robust-sorting=1") || + clue.contains("interleaving=")) { + return null; + } + } + return new AudioCodec(type, rtpmap, fmtp); } } diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index 37cc121..43a3827 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -20,13 +20,63 @@ import java.util.HashMap; import java.util.Map; /** + * An AudioGroup acts as a router connected to the speaker, the microphone, and + * {@link AudioStream}s. Its pipeline has four steps. First, for each + * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming + * packets and stores in its buffer. Then, if the microphone is enabled, + * processes the recorded audio and stores in its buffer. Third, if the speaker + * is enabled, mixes and playbacks buffers of all AudioStreams. Finally, for + * each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other + * buffers and sends back the encoded packets. An AudioGroup does nothing if + * there is no AudioStream in it. + * + *

Few things must be noticed before using these classes. The performance is + * highly related to the system load and the network bandwidth. Usually a + * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network + * bandwidth, and vise versa. Using two AudioStreams at the same time not only + * doubles the load but also the bandwidth. The condition varies from one device + * to another, and developers must choose the right combination in order to get + * the best result. + * + *

It is sometimes useful to keep multiple AudioGroups at the same time. For + * example, a Voice over IP (VoIP) application might want to put a conference + * call on hold in order to make a new call but still allow people in the + * previous call to talk to each other. This can be done easily using two + * AudioGroups, but there are some limitations. Since the speaker and the + * microphone are shared globally, only one AudioGroup is allowed to run in + * modes other than {@link #MODE_ON_HOLD}. In addition, before adding an + * AudioStream into an AudioGroup, one should always put all other AudioGroups + * into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly + * initialized. + * @hide */ -/** @hide */ public class AudioGroup { + /** + * This mode is similar to {@link #MODE_NORMAL} except the speaker and + * the microphone are disabled. + */ public static final int MODE_ON_HOLD = 0; + + /** + * This mode is similar to {@link #MODE_NORMAL} except the microphone is + * muted. + */ public static final int MODE_MUTED = 1; + + /** + * This mode indicates that the speaker, the microphone, and all + * {@link AudioStream}s in the group are enabled. First, the packets + * received from the streams are decoded and mixed with the audio recorded + * from the microphone. Then, the results are played back to the speaker, + * encoded and sent back to each stream. + */ public static final int MODE_NORMAL = 2; - public static final int MODE_EC_ENABLED = 3; + + /** + * This mode is similar to {@link #MODE_NORMAL} except the echo suppression + * is enabled. It should be only used when the speaker phone is on. + */ + public static final int MODE_ECHO_SUPPRESSION = 3; private final Map mStreams; private int mMode = MODE_ON_HOLD; @@ -36,23 +86,42 @@ public class AudioGroup { System.loadLibrary("rtp_jni"); } + /** + * Creates an empty AudioGroup. + */ public AudioGroup() { mStreams = new HashMap(); } + /** + * Returns the current mode. + */ public int getMode() { return mMode; } + /** + * Changes the current mode. It must be one of {@link #MODE_ON_HOLD}, + * {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and + * {@link #MODE_ECHO_SUPPRESSION}. + * + * @param mode The mode to change to. + * @throws IllegalArgumentException if the mode is invalid. + */ public synchronized native void setMode(int mode); - synchronized void add(AudioStream stream, AudioCodec codec, int codecType, int dtmfType) { + private native void add(int mode, int socket, String remoteAddress, + int remotePort, String codecSpec, int dtmfType); + + synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { if (!mStreams.containsKey(stream)) { try { int socket = stream.dup(); + String codecSpec = String.format("%d %s %s", codec.type, + codec.rtpmap, codec.fmtp); add(stream.getMode(), socket, - stream.getRemoteAddress().getHostAddress(), stream.getRemotePort(), - codec.name, codec.sampleRate, codec.sampleCount, codecType, dtmfType); + stream.getRemoteAddress().getHostAddress(), + stream.getRemotePort(), codecSpec, dtmfType); mStreams.put(stream, socket); } catch (NullPointerException e) { throw new IllegalStateException(e); @@ -60,8 +129,7 @@ public class AudioGroup { } } - private native void add(int mode, int socket, String remoteAddress, int remotePort, - String codecName, int sampleRate, int sampleCount, int codecType, int dtmfType); + private native void remove(int socket); synchronized void remove(AudioStream stream) { Integer socket = mStreams.remove(stream); @@ -70,8 +138,6 @@ public class AudioGroup { } } - private native void remove(int socket); - /** * Sends a DTMF digit to every {@link AudioStream} in this group. Currently * only event {@code 0} to {@code 15} are supported. @@ -80,13 +146,16 @@ public class AudioGroup { */ public native synchronized void sendDtmf(int event); - public synchronized void reset() { + /** + * Removes every {@link AudioStream} in this group. + */ + public synchronized void clear() { remove(-1); } @Override protected void finalize() throws Throwable { - reset(); + clear(); super.finalize(); } } diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index a955fd2..908aada 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -20,12 +20,27 @@ import java.net.InetAddress; import java.net.SocketException; /** - * AudioStream represents a RTP stream carrying audio payloads. + * An AudioStream is a {@link RtpStream} which carrys audio payloads over + * Real-time Transport Protocol (RTP). Two different classes are developed in + * order to support various usages such as audio conferencing. An AudioStream + * represents a remote endpoint which consists of a network mapping and a + * configured {@link AudioCodec}. On the other side, An {@link AudioGroup} + * represents a local endpoint which mixes all the AudioStreams and optionally + * interacts with the speaker and the microphone at the same time. The simplest + * usage includes one for each endpoints. For other combinations, users should + * be aware of the limitations described in {@link AudioGroup}. + * + *

An AudioStream becomes busy when it joins an AudioGroup. In this case most + * of the setter methods are disabled. This is designed to ease the task of + * managing native resources. One can always make an AudioStream leave its + * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it + * back after the modification is done. + * + * @see AudioGroup + * @hide */ -/** @hide */ public class AudioStream extends RtpStream { private AudioCodec mCodec; - private int mCodecType = -1; private int mDtmfType = -1; private AudioGroup mGroup; @@ -42,7 +57,8 @@ public class AudioStream extends RtpStream { } /** - * Returns {@code true} if the stream already joined an {@link AudioGroup}. + * Returns {@code true} if the stream has already joined an + * {@link AudioGroup}. */ @Override public final boolean isBusy() { @@ -52,7 +68,7 @@ public class AudioStream extends RtpStream { /** * Returns the joined {@link AudioGroup}. */ - public AudioGroup getAudioGroup() { + public AudioGroup getGroup() { return mGroup; } @@ -74,35 +90,26 @@ public class AudioStream extends RtpStream { mGroup = null; } if (group != null) { - group.add(this, mCodec, mCodecType, mDtmfType); + group.add(this, mCodec, mDtmfType); mGroup = group; } } /** - * Sets the {@link AudioCodec} and its RTP payload type. According to RFC - * 3551, the type must be in the range of 0 and 127, where 96 and above are - * dynamic types. For codecs with static mappings (non-negative - * {@link AudioCodec#defaultType}), assigning a different non-dynamic type - * is disallowed. + * Sets the {@link AudioCodec}. * * @param codec The AudioCodec to be used. - * @param type The RTP payload type. - * @throws IllegalArgumentException if the type is invalid or used by DTMF. + * @throws IllegalArgumentException if its type is used by DTMF. * @throws IllegalStateException if the stream is busy. */ - public void setCodec(AudioCodec codec, int type) { + public void setCodec(AudioCodec codec) { if (isBusy()) { throw new IllegalStateException("Busy"); } - if (type < 0 || type > 127 || (type != codec.defaultType && type < 96)) { - throw new IllegalArgumentException("Invalid type"); - } - if (type == mDtmfType) { + if (codec.type == mDtmfType) { throw new IllegalArgumentException("The type is used by DTMF"); } mCodec = codec; - mCodecType = type; } /** @@ -127,7 +134,7 @@ public class AudioStream extends RtpStream { if (type < 96 || type > 127) { throw new IllegalArgumentException("Invalid type"); } - if (type == mCodecType) { + if (type == mCodec.type) { throw new IllegalArgumentException("The type is used by codec"); } } diff --git a/java/android/net/rtp/RtpStream.java b/java/android/net/rtp/RtpStream.java index ef5ca17..23fb258 100644 --- a/java/android/net/rtp/RtpStream.java +++ b/java/android/net/rtp/RtpStream.java @@ -22,13 +22,25 @@ import java.net.Inet6Address; import java.net.SocketException; /** - * RtpStream represents a base class of media streams running over - * Real-time Transport Protocol (RTP). + * RtpStream represents the base class of streams which send and receive network + * packets with media payloads over Real-time Transport Protocol (RTP). + * @hide */ -/** @hide */ public class RtpStream { + /** + * This mode indicates that the stream sends and receives packets at the + * same time. This is the initial mode for new streams. + */ public static final int MODE_NORMAL = 0; + + /** + * This mode indicates that the stream only sends packets. + */ public static final int MODE_SEND_ONLY = 1; + + /** + * This mode indicates that the stream only receives packets. + */ public static final int MODE_RECEIVE_ONLY = 2; private final InetAddress mLocalAddress; @@ -89,15 +101,16 @@ public class RtpStream { } /** - * Returns {@code true} if the stream is busy. This method is intended to be - * overridden by subclasses. + * Returns {@code true} if the stream is busy. In this case most of the + * setter methods are disabled. This method is intended to be overridden + * by subclasses. */ public boolean isBusy() { return false; } /** - * Returns the current mode. The initial mode is {@link #MODE_NORMAL}. + * Returns the current mode. */ public int getMode() { return mMode; @@ -123,7 +136,8 @@ public class RtpStream { } /** - * Associates with a remote host. + * Associates with a remote host. This defines the destination of the + * outgoing packets. * * @param address The network address of the remote host. * @param port The network port of the remote host. -- cgit v1.2.3 From 07c7dc5e0b1465a83fbe33d3b94f7ebceccbdee0 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 21 Sep 2010 15:27:28 +0800 Subject: RTP: Add two getters to retrieve the current configuration from AudioStream. Change-Id: Iff588130653242f6ddd6a6b663df775ecb276768 --- java/android/net/rtp/AudioStream.java | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index 908aada..e5197ce 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -95,6 +95,15 @@ public class AudioStream extends RtpStream { } } + /** + * Returns the {@link AudioCodec}, or {@code null} if it is not set. + * + * @see #setCodec(AudioCodec) + */ + public AudioCodec getCodec() { + return mCodec; + } + /** * Sets the {@link AudioCodec}. * @@ -112,13 +121,23 @@ public class AudioStream extends RtpStream { mCodec = codec; } + /** + * Returns the RTP payload type for dual-tone multi-frequency (DTMF) digits, + * or {@code -1} if it is not enabled. + * + * @see #setDtmfType(int) + */ + public int getDtmfType() { + return mDtmfType; + } + /** * Sets the RTP payload type for dual-tone multi-frequency (DTMF) digits. * The primary usage is to send digits to the remote gateway to perform * certain tasks, such as second-stage dialing. According to RFC 2833, the * RTP payload type for DTMF is assigned dynamically, so it must be in the * range of 96 and 127. One can use {@code -1} to disable DTMF and free up - * the previous assigned value. This method cannot be called when the stream + * the previous assigned type. This method cannot be called when the stream * already joined an {@link AudioGroup}. * * @param type The RTP payload type to be used or {@code -1} to disable it. -- cgit v1.2.3 From 0d2c37c274f1db29f51f1d078f240be13b784bf2 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 16 Sep 2010 18:36:45 +0800 Subject: RTP: Update native part to reflect the API change. Change-Id: Ic2858920ad77d7312f2429f89ca509a481363431 --- jni/rtp/AudioCodec.cpp | 18 ++++++++------- jni/rtp/AudioCodec.h | 6 +++-- jni/rtp/AudioGroup.cpp | 63 +++++++++++++++++++++++++++----------------------- 3 files changed, 48 insertions(+), 39 deletions(-) diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index ddd07fc..4d8d36c 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -36,9 +36,9 @@ int8_t gExponents[128] = { class UlawCodec : public AudioCodec { public: - bool set(int sampleRate, int sampleCount) { - mSampleCount = sampleCount; - return sampleCount > 0; + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; } int encode(void *payload, int16_t *samples); int decode(int16_t *samples, void *payload, int length); @@ -89,9 +89,9 @@ AudioCodec *newUlawCodec() class AlawCodec : public AudioCodec { public: - bool set(int sampleRate, int sampleCount) { - mSampleCount = sampleCount; - return sampleCount > 0; + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; } int encode(void *payload, int16_t *samples); int decode(int16_t *samples, void *payload, int length); @@ -152,8 +152,10 @@ AudioCodec *newAudioCodec(const char *codecName) { AudioCodecType *type = gAudioCodecTypes; while (type->name != NULL) { - if (strcmp(codecName, type->name) == 0) { - return type->create(); + if (strcasecmp(codecName, type->name) == 0) { + AudioCodec *codec = type->create(); + codec->name = type->name; + return codec; } ++type; } diff --git a/jni/rtp/AudioCodec.h b/jni/rtp/AudioCodec.h index 797494c..e389255 100644 --- a/jni/rtp/AudioCodec.h +++ b/jni/rtp/AudioCodec.h @@ -22,9 +22,11 @@ class AudioCodec { public: + const char *name; + // Needed by destruction through base class pointers. virtual ~AudioCodec() {} - // Returns true if initialization succeeds. - virtual bool set(int sampleRate, int sampleCount) = 0; + // Returns sampleCount or non-positive value if unsupported. + virtual int set(int sampleRate, const char *fmtp) = 0; // Returns the length of payload in bytes. virtual int encode(void *payload, int16_t *samples) = 0; // Returns the number of decoded samples. diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 3433dcf..726e98b 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -77,7 +77,7 @@ public: AudioStream(); ~AudioStream(); bool set(int mode, int socket, sockaddr_storage *remote, - const char *codecName, int sampleRate, int sampleCount, + AudioCodec *codec, int sampleRate, int sampleCount, int codecType, int dtmfType); void sendDtmf(int event); @@ -140,7 +140,7 @@ AudioStream::~AudioStream() } bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, - const char *codecName, int sampleRate, int sampleCount, + AudioCodec *codec, int sampleRate, int sampleCount, int codecType, int dtmfType) { if (mode < 0 || mode > LAST_MODE) { @@ -148,14 +148,6 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, } mMode = mode; - if (codecName) { - mRemote = *remote; - mCodec = newAudioCodec(codecName); - if (!mCodec || !mCodec->set(sampleRate, sampleCount)) { - return false; - } - } - mCodecMagic = (0x8000 | codecType) << 16; mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16; @@ -181,11 +173,15 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, mDtmfEvent = -1; mDtmfStart = 0; - // Only take over the socket when succeeded. + // Only take over these things when succeeded. mSocket = socket; + if (codec) { + mRemote = *remote; + mCodec = codec; + } LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, - (codecName ? codecName : "RAW"), mSampleRate, mInterval); + (codec ? codec->name : "RAW"), mSampleRate, mInterval); return true; } @@ -831,10 +827,9 @@ static jfieldID gMode; void add(JNIEnv *env, jobject thiz, jint mode, jint socket, jstring jRemoteAddress, jint remotePort, - jstring jCodecName, jint sampleRate, jint sampleCount, - jint codecType, jint dtmfType) + jstring jCodecSpec, jint dtmfType) { - const char *codecName = NULL; + AudioCodec *codec = NULL; AudioStream *stream = NULL; AudioGroup *group = NULL; @@ -842,33 +837,42 @@ void add(JNIEnv *env, jobject thiz, jint mode, sockaddr_storage remote; if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { // Exception already thrown. - goto error; - } - if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - goto error; + return; } - if (!jCodecName) { - jniThrowNullPointerException(env, "codecName"); - goto error; + if (!jCodecSpec) { + jniThrowNullPointerException(env, "codecSpec"); + return; } - codecName = env->GetStringUTFChars(jCodecName, NULL); - if (!codecName) { + const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL); + if (!codecSpec) { // Exception already thrown. + return; + } + + // Create audio codec. + int codecType = -1; + char codecName[16]; + int sampleRate = -1; + sscanf(codecSpec, "%d %[^/]%*c%d", &codecType, codecName, &sampleRate); + codec = newAudioCodec(codecName); + int sampleCount = (codec ? codec->set(sampleRate, codecSpec) : -1); + env->ReleaseStringUTFChars(jCodecSpec, codecSpec); + if (sampleCount <= 0) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot initialize audio codec"); goto error; } // Create audio stream. stream = new AudioStream; - if (!stream->set(mode, socket, &remote, codecName, sampleRate, sampleCount, + if (!stream->set(mode, socket, &remote, codec, sampleRate, sampleCount, codecType, dtmfType)) { jniThrowException(env, "java/lang/IllegalStateException", "cannot initialize audio stream"); - env->ReleaseStringUTFChars(jCodecName, codecName); goto error; } - env->ReleaseStringUTFChars(jCodecName, codecName); socket = -1; + codec = NULL; // Create audio group. group = (AudioGroup *)env->GetIntField(thiz, gNative); @@ -896,6 +900,7 @@ void add(JNIEnv *env, jobject thiz, jint mode, error: delete group; delete stream; + delete codec; close(socket); env->SetIntField(thiz, gNative, NULL); } @@ -930,7 +935,7 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event) } JNINativeMethod gMethods[] = { - {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)V", (void *)add}, + {"add", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add}, {"remove", "(I)V", (void *)remove}, {"setMode", "(I)V", (void *)setMode}, {"sendDtmf", "(I)V", (void *)sendDtmf}, -- cgit v1.2.3 From ea7bed2487a9460ad2c01cda9ded8035e0b0c945 Mon Sep 17 00:00:00 2001 From: repo sync Date: Thu, 23 Sep 2010 05:46:01 +0800 Subject: RTP: Add log throttle for "no data". Change-Id: I14d9886a40fa780514cbc6c5bac6fb2a670f55f4 --- jni/rtp/AudioGroup.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 726e98b..7cf0613 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -104,6 +104,7 @@ private: int mSampleRate; int mSampleCount; int mInterval; + int mLogThrottle; int16_t *mBuffer; int mBufferMask; @@ -278,7 +279,10 @@ void AudioStream::encode(int tick, AudioStream *chain) chain = chain->mNext; } if (!mixed) { - LOGD("stream[%d] no data", mSocket); + if ((mTick ^ mLogThrottle) >> 10) { + mLogThrottle = mTick; + LOGD("stream[%d] no data", mSocket); + } return; } -- cgit v1.2.3 From 7d51a25fe714d14df79cf71d06cf83d88b479759 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Mon, 20 Sep 2010 16:34:18 +0800 Subject: SDP: Add a simple class to help manipulate session descriptions. Change-Id: I1631ee20e8b4a9ad8e2184356b5d13de66e03db1 --- java/android/net/sip/SimpleSessionDescription.java | 612 +++++++++++++++++++++ 1 file changed, 612 insertions(+) create mode 100644 java/android/net/sip/SimpleSessionDescription.java diff --git a/java/android/net/sip/SimpleSessionDescription.java b/java/android/net/sip/SimpleSessionDescription.java new file mode 100644 index 0000000..733a5f6 --- /dev/null +++ b/java/android/net/sip/SimpleSessionDescription.java @@ -0,0 +1,612 @@ +/* + * 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 java.util.ArrayList; +import java.util.Arrays; + +/** + * An object used to manipulate messages of Session Description Protocol (SDP). + * It is mainly designed for the uses of Session Initiation Protocol (SIP). + * Therefore, it only handles connection addresses ("c="), bandwidth limits, + * ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this + * implementation does not support multicast sessions. + * + *

Here is an example code to create a session description.

+ *
+ * SimpleSessionDescription description = new SimpleSessionDescription(
+ *     System.currentTimeMillis(), "1.2.3.4");
+ * Media media = description.newMedia("audio", 56789, 1, "RTP/AVP");
+ * media.setRtpPayload(0, "PCMU/8000", null);
+ * media.setRtpPayload(8, "PCMA/8000", null);
+ * media.setRtpPayload(127, "telephone-event/8000", "0-15");
+ * media.setAttribute("sendrecv", "");
+ * 
+ *

Invoking description.encode() will produce a result like the + * one below.

+ *
+ * v=0
+ * o=- 1284970442706 1284970442709 IN IP4 1.2.3.4
+ * s=-
+ * c=IN IP4 1.2.3.4
+ * t=0 0
+ * m=audio 56789 RTP/AVP 0 8 127
+ * a=rtpmap:0 PCMU/8000
+ * a=rtpmap:8 PCMA/8000
+ * a=rtpmap:127 telephone-event/8000
+ * a=fmtp:127 0-15
+ * a=sendrecv
+ * 
+ * @hide + */ +public class SimpleSessionDescription { + private final Fields mFields = new Fields("voscbtka"); + private final ArrayList mMedia = new ArrayList(); + + /** + * Creates a minimal session description from the given session ID and + * unicast address. The address is used in the origin field ("o=") and the + * connection field ("c="). See {@link SimpleSessionDescription} for an + * example of its usage. + */ + public SimpleSessionDescription(long sessionId, String address) { + address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address; + mFields.parse("v=0"); + mFields.parse(String.format("o=- %d %d %s", sessionId, + System.currentTimeMillis(), address)); + mFields.parse("s=-"); + mFields.parse("t=0 0"); + mFields.parse("c=" + address); + } + + /** + * Creates a session description from the given message. + * + * @throws IllegalArgumentException if message is invalid. + */ + public SimpleSessionDescription(String message) { + String[] lines = message.trim().replaceAll(" +", " ").split("[\r\n]+"); + Fields fields = mFields; + + for (String line : lines) { + try { + if (line.charAt(1) != '=') { + throw new IllegalArgumentException(); + } + if (line.charAt(0) == 'm') { + String[] parts = line.substring(2).split(" ", 4); + String[] ports = parts[1].split("/", 2); + Media media = newMedia(parts[0], Integer.parseInt(ports[0]), + (ports.length < 2) ? 1 : Integer.parseInt(ports[1]), + parts[2]); + for (String format : parts[3].split(" ")) { + media.setFormat(format, null); + } + fields = media; + } else { + fields.parse(line); + } + } catch (Exception e) { + throw new IllegalArgumentException("Invalid SDP: " + line); + } + } + } + + /** + * Creates a new media description in this session description. + * + * @param type The media type, e.g. {@code "audio"}. + * @param port The first transport port used by this media. + * @param portCount The number of contiguous ports used by this media. + * @param protocol The transport protocol, e.g. {@code "RTP/AVP"}. + */ + public Media newMedia(String type, int port, int portCount, + String protocol) { + Media media = new Media(type, port, portCount, protocol); + mMedia.add(media); + return media; + } + + /** + * Returns all the media descriptions in this session description. + */ + public Media[] getMedia() { + return mMedia.toArray(new Media[mMedia.size()]); + } + + /** + * Encodes the session description and all its media descriptions in a + * string. Note that the result might be incomplete if a required field + * has never been added before. + */ + public String encode() { + StringBuilder buffer = new StringBuilder(); + mFields.write(buffer); + for (Media media : mMedia) { + media.write(buffer); + } + return buffer.toString(); + } + + /** + * Returns the connection address or {@code null} if it is not present. + */ + public String getAddress() { + return mFields.getAddress(); + } + + /** + * Sets the connection address. The field will be removed if the address + * is {@code null}. + */ + public void setAddress(String address) { + mFields.setAddress(address); + } + + /** + * Returns the encryption method or {@code null} if it is not present. + */ + public String getEncryptionMethod() { + return mFields.getEncryptionMethod(); + } + + /** + * Returns the encryption key or {@code null} if it is not present. + */ + public String getEncryptionKey() { + return mFields.getEncryptionKey(); + } + + /** + * Sets the encryption method and the encryption key. The field will be + * removed if the method is {@code null}. + */ + public void setEncryption(String method, String key) { + mFields.setEncryption(method, key); + } + + /** + * Returns the types of the bandwidth limits. + */ + public String[] getBandwidthTypes() { + return mFields.getBandwidthTypes(); + } + + /** + * Returns the bandwidth limit of the given type or {@code -1} if it is not + * present. + */ + public int getBandwidth(String type) { + return mFields.getBandwidth(type); + } + + /** + * Sets the bandwith limit for the given type. The field will be removed if + * the value is negative. + */ + public void setBandwidth(String type, int value) { + mFields.setBandwidth(type, value); + } + + /** + * Returns the names of all the attributes. + */ + public String[] getAttributeNames() { + return mFields.getAttributeNames(); + } + + /** + * Returns the attribute of the given name or {@code null} if it is not + * present. + */ + public String getAttribute(String name) { + return mFields.getAttribute(name); + } + + /** + * Sets the attribute for the given name. The field will be removed if + * the value is {@code null}. To set a binary attribute, use an empty + * string as the value. + */ + public void setAttribute(String name, String value) { + mFields.setAttribute(name, value); + } + + /** + * This class represents a media description of a session description. It + * can only be created by {@link SimpleSessionDescription#newMedia}. Since + * the syntax is more restricted for RTP based protocols, two sets of access + * methods are implemented. See {@link SimpleSessionDescription} for an + * example of its usage. + */ + public static class Media extends Fields { + private final String mType; + private final int mPort; + private final int mPortCount; + private final String mProtocol; + private ArrayList mFormats = new ArrayList(); + + private Media(String type, int port, int portCount, String protocol) { + super("icbka"); + mType = type; + mPort = port; + mPortCount = portCount; + mProtocol = protocol; + } + + /** + * Returns the media type. + */ + public String getType() { + return mType; + } + + /** + * Returns the first transport port used by this media. + */ + public int getPort() { + return mPort; + } + + /** + * Returns the number of contiguous ports used by this media. + */ + public int getPortCount() { + return mPortCount; + } + + /** + * Returns the transport protocol. + */ + public String getProtocol() { + return mProtocol; + } + + /** + * Returns the media formats. + */ + public String[] getFormats() { + return mFormats.toArray(new String[mFormats.size()]); + } + + /** + * Returns the {@code fmtp} attribute of the given format or + * {@code null} if it is not present. + */ + public String getFmtp(String format) { + return super.get("a=fmtp:" + format, ' '); + } + + /** + * Sets a format and its {@code fmtp} attribute. If the attribute is + * {@code null}, the corresponding field will be removed. + */ + public void setFormat(String format, String fmtp) { + mFormats.remove(format); + mFormats.add(format); + super.set("a=rtpmap:" + format, ' ', null); + super.set("a=fmtp:" + format, ' ', fmtp); + } + + /** + * Removes a format and its {@code fmtp} attribute. + */ + public void removeFormat(String format) { + mFormats.remove(format); + super.set("a=rtpmap:" + format, ' ', null); + super.set("a=fmtp:" + format, ' ', null); + } + + /** + * Returns the RTP payload types. + */ + public int[] getRtpPayloadTypes() { + int[] types = new int[mFormats.size()]; + int length = 0; + for (String format : mFormats) { + try { + types[length] = Integer.parseInt(format); + ++length; + } catch (NumberFormatException e) { } + } + return Arrays.copyOf(types, length); + } + + /** + * Returns the {@code rtpmap} attribute of the given RTP payload type + * or {@code null} if it is not present. + */ + public String getRtpmap(int type) { + return super.get("a=rtpmap:" + type, ' '); + } + + /** + * Returns the {@code fmtp} attribute of the given RTP payload type or + * {@code null} if it is not present. + */ + public String getFmtp(int type) { + return super.get("a=fmtp:" + type, ' '); + } + + /** + * Sets a RTP payload type and its {@code rtpmap} and {@fmtp} + * attributes. If any of the attributes is {@code null}, the + * corresponding field will be removed. See + * {@link SimpleSessionDescription} for an example of its usage. + */ + public void setRtpPayload(int type, String rtpmap, String fmtp) { + String format = String.valueOf(type); + mFormats.remove(format); + mFormats.add(format); + super.set("a=rtpmap:" + format, ' ', rtpmap); + super.set("a=fmtp:" + format, ' ', fmtp); + } + + /** + * Removes a RTP payload and its {@code rtpmap} and {@code fmtp} + * attributes. + */ + public void removeRtpPayload(int type) { + removeFormat(String.valueOf(type)); + } + + private void write(StringBuilder buffer) { + buffer.append("m=").append(mType).append(' ').append(mPort); + if (mPortCount != 1) { + buffer.append('/').append(mPortCount); + } + buffer.append(' ').append(mProtocol); + for (String format : mFormats) { + buffer.append(' ').append(format); + } + buffer.append("\r\n"); + super.write(buffer); + } + } + + /** + * This class acts as a set of fields, and the size of the set is expected + * to be small. Therefore, it uses a simple list instead of maps. Each field + * has three parts: a key, a delimiter, and a value. Delimiters are special + * because they are not included in binary attributes. As a result, the + * private methods, which are the building blocks of this class, all take + * the delimiter as an argument. + */ + private static class Fields { + private final String mOrder; + private final ArrayList mLines = new ArrayList(); + + Fields(String order) { + mOrder = order; + } + + /** + * Returns the connection address or {@code null} if it is not present. + */ + public String getAddress() { + String address = get("c", '='); + if (address == null) { + return null; + } + String[] parts = address.split(" "); + if (parts.length != 3) { + return null; + } + int slash = parts[2].indexOf('/'); + return (slash < 0) ? parts[2] : parts[2].substring(0, slash); + } + + /** + * Sets the connection address. The field will be removed if the address + * is {@code null}. + */ + public void setAddress(String address) { + if (address != null) { + address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + + address; + } + set("c", '=', address); + } + + /** + * Returns the encryption method or {@code null} if it is not present. + */ + public String getEncryptionMethod() { + String encryption = get("k", '='); + if (encryption == null) { + return null; + } + int colon = encryption.indexOf(':'); + return (colon == -1) ? encryption : encryption.substring(0, colon); + } + + /** + * Returns the encryption key or {@code null} if it is not present. + */ + public String getEncryptionKey() { + String encryption = get("k", '='); + if (encryption == null) { + return null; + } + int colon = encryption.indexOf(':'); + return (colon == -1) ? null : encryption.substring(0, colon + 1); + } + + /** + * Sets the encryption method and the encryption key. The field will be + * removed if the method is {@code null}. + */ + public void setEncryption(String method, String key) { + set("k", '=', (method == null || key == null) ? + method : method + ':' + key); + } + + /** + * Returns the types of the bandwidth limits. + */ + public String[] getBandwidthTypes() { + return cut("b=", ':'); + } + + /** + * Returns the bandwidth limit of the given type or {@code -1} if it is + * not present. + */ + public int getBandwidth(String type) { + String value = get("b=" + type, ':'); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { } + setBandwidth(type, -1); + } + return -1; + } + + /** + * Sets the bandwith limit for the given type. The field will be removed + * if the value is negative. + */ + public void setBandwidth(String type, int value) { + set("b=" + type, ':', (value < 0) ? null : String.valueOf(value)); + } + + /** + * Returns the names of all the attributes. + */ + public String[] getAttributeNames() { + return cut("a=", ':'); + } + + /** + * Returns the attribute of the given name or {@code null} if it is not + * present. + */ + public String getAttribute(String name) { + return get("a=" + name, ':'); + } + + /** + * Sets the attribute for the given name. The field will be removed if + * the value is {@code null}. To set a binary attribute, use an empty + * string as the value. + */ + public void setAttribute(String name, String value) { + set("a=" + name, ':', value); + } + + private void write(StringBuilder buffer) { + for (int i = 0; i < mOrder.length(); ++i) { + char type = mOrder.charAt(i); + for (String line : mLines) { + if (line.charAt(0) == type) { + buffer.append(line).append("\r\n"); + } + } + } + } + + /** + * Invokes {@link #set} after splitting the line into three parts. + */ + private void parse(String line) { + char type = line.charAt(0); + if (mOrder.indexOf(type) == -1) { + return; + } + char delimiter = '='; + if (line.startsWith("a=rtpmap:") || line.startsWith("a=fmtp:")) { + delimiter = ' '; + } else if (type == 'b' || type == 'a') { + delimiter = ':'; + } + int i = line.indexOf(delimiter); + if (i == -1) { + set(line, delimiter, ""); + } else { + set(line.substring(0, i), delimiter, line.substring(i + 1)); + } + } + + /** + * Finds the key with the given prefix and returns its suffix. + */ + private String[] cut(String prefix, char delimiter) { + String[] names = new String[mLines.size()]; + int length = 0; + for (String line : mLines) { + if (line.startsWith(prefix)) { + int i = line.indexOf(delimiter); + if (i == -1) { + i = line.length(); + } + names[length] = line.substring(prefix.length(), i); + ++length; + } + } + return Arrays.copyOf(names, length); + } + + /** + * Returns the index of the key. + */ + private int find(String key, char delimiter) { + int length = key.length(); + for (int i = mLines.size() - 1; i >= 0; --i) { + String line = mLines.get(i); + if (line.startsWith(key) && (line.length() == length || + line.charAt(length) == delimiter)) { + return i; + } + } + return -1; + } + + /** + * Sets the key with the value or removes the key if the value is + * {@code null}. + */ + private void set(String key, char delimiter, String value) { + int index = find(key, delimiter); + if (value != null) { + if (value.length() != 0) { + key = key + delimiter + value; + } + if (index == -1) { + mLines.add(key); + } else { + mLines.set(index, key); + } + } else if (index != -1) { + mLines.remove(index); + } + } + + /** + * Returns the value of the key. + */ + private String get(String key, char delimiter) { + int index = find(key, delimiter); + if (index == -1) { + return null; + } + String line = mLines.get(index); + int length = key.length(); + return (line.length() == length) ? "" : line.substring(length + 1); + } + } +} -- cgit v1.2.3 From e436bd1859727dd5690c7c758b70c8ef476e2662 Mon Sep 17 00:00:00 2001 From: repo sync Date: Thu, 23 Sep 2010 07:12:30 +0800 Subject: SIP: Make SipAudioCallImpl use SimpleSessionDescription instead of javax.sdp. Change-Id: I7efff4f29ca84c3e7c17ef066b7186b514a777b2 --- java/android/net/sip/SipAudioCallImpl.java | 381 ++++++++++++++--------------- 1 file changed, 185 insertions(+), 196 deletions(-) diff --git a/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java index ccf4d15..2f8d175 100644 --- a/java/android/net/sip/SipAudioCallImpl.java +++ b/java/android/net/sip/SipAudioCallImpl.java @@ -16,8 +16,6 @@ package android.net.sip; -import gov.nist.javax.sdp.fields.SDPKeywords; - import android.content.Context; import android.media.AudioManager; import android.media.Ringtone; @@ -28,6 +26,7 @@ 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; @@ -38,15 +37,13 @@ import android.util.Log; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.sdp.SdpException; /** - * Class that handles an audio call over SIP. + * Class that handles an audio call over SIP. */ /** @hide */ public class SipAudioCallImpl extends SipSessionAdapter @@ -54,20 +51,19 @@ public class SipAudioCallImpl extends SipSessionAdapter 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 String AUDIO = "audio"; - private static final int DTMF = 101; private static final int SESSION_TIMEOUT = 5; // in seconds private Context mContext; private SipProfile mLocalProfile; private SipAudioCall.Listener mListener; private ISipSession mSipSession; - private SdpSessionDescription mPeerSd; + + private long mSessionId = System.currentTimeMillis(); + private String mPeerSd; private AudioStream mAudioStream; private AudioGroup mAudioGroup; - private SdpSessionDescription.AudioCodec mCodec; - private long mSessionId = -1L; // SDP session ID + private boolean mInCall = false; private boolean mMuted = false; private boolean mHold = false; @@ -149,7 +145,7 @@ public class SipAudioCallImpl extends SipSessionAdapter mInCall = false; mHold = false; - mSessionId = -1L; + mSessionId = System.currentTimeMillis(); mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; @@ -229,8 +225,8 @@ public class SipAudioCallImpl extends SipSessionAdapter // session changing request try { - mPeerSd = new SdpSessionDescription(sessionDescription); - answerCall(SESSION_TIMEOUT); + String answer = createAnswer(sessionDescription).encode(); + mSipSession.answerCall(answer, SESSION_TIMEOUT); } catch (Throwable e) { Log.e(TAG, "onRinging()", e); session.endCall(); @@ -245,12 +241,8 @@ public class SipAudioCallImpl extends SipSessionAdapter String sessionDescription) { stopRingbackTone(); stopRinging(); - try { - mPeerSd = new SdpSessionDescription(sessionDescription); - Log.d(TAG, "sip call established: " + mPeerSd); - } catch (SdpException e) { - Log.e(TAG, "createSessionDescription()", e); - } + mPeerSd = sessionDescription; + Log.v(TAG, "onCallEstablished()" + mPeerSd); Listener listener = mListener; if (listener != null) { @@ -335,10 +327,10 @@ public class SipAudioCallImpl extends SipSessionAdapter public synchronized void attachCall(ISipSession session, String sessionDescription) throws SipException { mSipSession = session; + mPeerSd = sessionDescription; + Log.v(TAG, "attachCall()" + mPeerSd); try { - mPeerSd = new SdpSessionDescription(sessionDescription); session.setListener(this); - if (getState() == SipSessionState.INCOMING_CALL) startRinging(); } catch (Throwable e) { Log.e(TAG, "attachCall()", e); @@ -354,8 +346,8 @@ public class SipAudioCallImpl extends SipSessionAdapter throw new SipException( "Failed to create SipSession; network available?"); } - mSipSession.makeCall(peerProfile, createOfferSessionDescription(), - timeout); + mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); + mSipSession.makeCall(peerProfile, createOffer().encode(), timeout); } catch (Throwable e) { if (e instanceof SipException) { throw (SipException) e; @@ -368,7 +360,7 @@ public class SipAudioCallImpl extends SipSessionAdapter public synchronized void endCall() throws SipException { try { stopRinging(); - stopCall(true); + stopCall(RELEASE_SOCKET); mInCall = false; // perform the above local ops first and then network op @@ -378,72 +370,131 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - public synchronized void holdCall(int timeout) throws SipException { - if (mHold) return; + public synchronized void answerCall(int timeout) throws SipException { try { - mSipSession.changeCall(createHoldSessionDescription(), timeout); - mHold = true; + stopRinging(); + mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); + mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); } catch (Throwable e) { + Log.e(TAG, "answerCall()", e); throwSipException(e); } - - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); } - public synchronized void answerCall(int timeout) throws SipException { + public synchronized void holdCall(int timeout) throws SipException { + if (mHold) return; try { - stopRinging(); - mSipSession.answerCall(createAnswerSessionDescription(), timeout); + mSipSession.changeCall(createHoldOffer().encode(), timeout); } catch (Throwable e) { - Log.e(TAG, "answerCall()", 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 { - mHold = false; - mSipSession.changeCall(createContinueSessionDescription(), timeout); + mSipSession.changeCall(createContinueOffer().encode(), timeout); } catch (Throwable e) { throwSipException(e); } - + mHold = false; AudioGroup audioGroup = getAudioGroup(); if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); } - private String createOfferSessionDescription() { - AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs(); - return createSdpBuilder(true, convert(codecs)).build(); - } - - private String createAnswerSessionDescription() { - try { - // choose an acceptable media from mPeerSd to answer - SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd); - SdpSessionDescription.Builder sdpBuilder = - createSdpBuilder(false, codec); - if (mPeerSd.isSendOnly(AUDIO)) { - sdpBuilder.addMediaAttribute(AUDIO, "recvonly", (String) null); - } else if (mPeerSd.isReceiveOnly(AUDIO)) { - sdpBuilder.addMediaAttribute(AUDIO, "sendonly", (String) null); + 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); } - return sdpBuilder.build(); - } catch (SdpException e) { - throw new RuntimeException(e); } + if (codec == null) { + throw new IllegalStateException("Reject SDP: no suitable codecs"); + } + return answer; } - private String createHoldSessionDescription() { - try { - return createSdpBuilder(false, mCodec) - .addMediaAttribute(AUDIO, "sendonly", (String) null) - .build(); - } catch (SdpException e) { - throw new RuntimeException(e); + 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() { @@ -468,57 +519,6 @@ public class SipAudioCallImpl extends SipSessionAdapter return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; } - private String createContinueSessionDescription() { - return createSdpBuilder(true, mCodec).build(); - } - - private String getMediaDescription(SdpSessionDescription.AudioCodec codec) { - return String.format("%d %s/%d", codec.payloadType, codec.name, - codec.sampleRate); - } - - private long getSessionId() { - if (mSessionId < 0) { - mSessionId = System.currentTimeMillis(); - } - return mSessionId; - } - - private SdpSessionDescription.Builder createSdpBuilder( - boolean addTelephoneEvent, - SdpSessionDescription.AudioCodec... codecs) { - String localIp = getLocalIp(); - SdpSessionDescription.Builder sdpBuilder; - try { - long sessionVersion = System.currentTimeMillis(); - sdpBuilder = new SdpSessionDescription.Builder("SIP Call") - .setOrigin(mLocalProfile, getSessionId(), sessionVersion, - SDPKeywords.IN, SDPKeywords.IPV4, localIp) - .setConnectionInfo(SDPKeywords.IN, SDPKeywords.IPV4, - localIp); - List codecIds = new ArrayList(); - for (SdpSessionDescription.AudioCodec codec : codecs) { - codecIds.add(codec.payloadType); - } - if (addTelephoneEvent) codecIds.add(DTMF); - sdpBuilder.addMedia(AUDIO, getLocalMediaPort(), 1, "RTP/AVP", - codecIds.toArray(new Integer[codecIds.size()])); - for (SdpSessionDescription.AudioCodec codec : codecs) { - sdpBuilder.addMediaAttribute(AUDIO, "rtpmap", - getMediaDescription(codec)); - } - if (addTelephoneEvent) { - sdpBuilder.addMediaAttribute(AUDIO, "rtpmap", - DTMF + " telephone-event/8000"); - } - // FIXME: deal with vbr codec - sdpBuilder.addMediaAttribute(AUDIO, "ptime", "20"); - } catch (SdpException e) { - throw new RuntimeException(e); - } - return sdpBuilder; - } - public synchronized void toggleMute() { AudioGroup audioGroup = getAudioGroup(); if (audioGroup != null) { @@ -557,49 +557,16 @@ public class SipAudioCallImpl extends SipSessionAdapter public synchronized AudioGroup getAudioGroup() { if (mAudioGroup != null) return mAudioGroup; - return ((mAudioStream == null) ? null : mAudioStream.getAudioGroup()); + return ((mAudioStream == null) ? null : mAudioStream.getGroup()); } public synchronized void setAudioGroup(AudioGroup group) { - if ((mAudioStream != null) && (mAudioStream.getAudioGroup() != null)) { + if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { mAudioStream.join(group); } mAudioGroup = group; } - private SdpSessionDescription.AudioCodec getCodec(SdpSessionDescription sd) { - HashMap acceptableCodecs = - new HashMap(); - for (AudioCodec codec : AudioCodec.getSystemSupportedCodecs()) { - acceptableCodecs.put(codec.name, codec); - } - for (SdpSessionDescription.AudioCodec codec : sd.getAudioCodecs()) { - AudioCodec matchedCodec = acceptableCodecs.get(codec.name); - if (matchedCodec != null) return codec; - } - Log.w(TAG, "no common codec is found, use PCM/0"); - return convert(AudioCodec.ULAW); - } - - private AudioCodec convert(SdpSessionDescription.AudioCodec codec) { - AudioCodec c = AudioCodec.getSystemSupportedCodec(codec.name); - return ((c == null) ? AudioCodec.ULAW : c); - } - - private SdpSessionDescription.AudioCodec convert(AudioCodec codec) { - return new SdpSessionDescription.AudioCodec(codec.defaultType, - codec.name, codec.sampleRate, codec.sampleCount); - } - - private SdpSessionDescription.AudioCodec[] convert(AudioCodec[] codecs) { - SdpSessionDescription.AudioCodec[] copies = - new SdpSessionDescription.AudioCodec[codecs.length]; - for (int i = 0, len = codecs.length; i < len; i++) { - copies[i] = convert(codecs[i]); - } - return copies; - } - public void startAudio() { try { startAudioInternal(); @@ -613,42 +580,77 @@ public class SipAudioCallImpl extends SipSessionAdapter } 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; - SdpSessionDescription peerSd = mPeerSd; - if (isWifiOn()) grabWifiHighPerfLock(); - String peerMediaAddress = peerSd.getPeerMediaAddress(AUDIO); - // TODO: handle multiple media fields - int peerMediaPort = peerSd.getPeerMediaPort(AUDIO); - Log.i(TAG, "start audiocall " + peerMediaAddress + ":" + peerMediaPort); - - int localPort = getLocalMediaPort(); - int sampleRate = 8000; - int frameSize = sampleRate / 50; // 160 - - // TODO: get sample rate from sdp - mCodec = getCodec(peerSd); - - AudioStream audioStream = mAudioStream; - audioStream.associate(InetAddress.getByName(peerMediaAddress), - peerMediaPort); - audioStream.setCodec(convert(mCodec), mCodec.payloadType); - audioStream.setDtmfType(DTMF); - Log.d(TAG, "start media: localPort=" + localPort + ", peer=" - + peerMediaAddress + ":" + peerMediaPort); - - audioStream.setMode(RtpStream.MODE_NORMAL); - if (!mHold) { - // FIXME: won't work if peer is not sending nor receiving - if (!peerSd.isSending(AUDIO)) { - Log.d(TAG, " not receiving"); - audioStream.setMode(RtpStream.MODE_SEND_ONLY); - } - if (!peerSd.isReceiving(AUDIO)) { - Log.d(TAG, " not sending"); - audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); + + // 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. @@ -668,7 +670,7 @@ public class SipAudioCallImpl extends SipSessionAdapter // there's another AudioGroup out there that's active } else { if (audioGroup == null) audioGroup = new AudioGroup(); - audioStream.join(audioGroup); + mAudioStream.join(audioGroup); if (mMuted) { audioGroup.setMode(AudioGroup.MODE_MUTED); } else { @@ -690,24 +692,11 @@ public class SipAudioCallImpl extends SipSessionAdapter } } - private int getLocalMediaPort() { - if (mAudioStream != null) return mAudioStream.getLocalPort(); - try { - AudioStream s = mAudioStream = - new AudioStream(InetAddress.getByName(getLocalIp())); - return s.getLocalPort(); - } catch (IOException e) { - Log.w(TAG, "getLocalMediaPort(): " + e); - throw new RuntimeException(e); - } - } - private String getLocalIp() { try { return mSipSession.getLocalIp(); } catch (RemoteException e) { - // FIXME - return "127.0.0.1"; + throw new IllegalStateException(e); } } -- cgit v1.2.3 From 129cdb5fa30ddc244d0a2dd3b0bc651611b39b8c Mon Sep 17 00:00:00 2001 From: repo sync Date: Thu, 23 Sep 2010 14:52:24 +0800 Subject: Fix the build. Change-Id: I82210cb2d41f532583f83ea17e6f2d8d49280a30 --- java/android/net/sip/SimpleSessionDescription.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/android/net/sip/SimpleSessionDescription.java b/java/android/net/sip/SimpleSessionDescription.java index 733a5f6..29166dc 100644 --- a/java/android/net/sip/SimpleSessionDescription.java +++ b/java/android/net/sip/SimpleSessionDescription.java @@ -343,7 +343,7 @@ public class SimpleSessionDescription { } /** - * Sets a RTP payload type and its {@code rtpmap} and {@fmtp} + * Sets a RTP payload type and its {@code rtpmap} and {@code fmtp} * attributes. If any of the attributes is {@code null}, the * corresponding field will be removed. See * {@link SimpleSessionDescription} for an example of its usage. -- cgit v1.2.3 From 3adf1946e78b52686fa5458e24645b05da57dc22 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Sep 2010 04:11:32 +0800 Subject: Refactoring SIP classes to get ready for API review. + replace SipAudioCall and its Listener interfaces with real implementations, + remove SipAudioCallImpl.java, most of it is has become part of SipAudioCall, + add SipSession and its Listener classes to wrap ISipSession and ISipSessionListener, + move SipSessionState to SipSession.State, + make SipManager keep context and remove the context argument from many methods of its, + rename SipManager.getInstance() to newInstance(), + rename constant names for action strings and extra keys to follow conventions, + set thread names for debugging purpose. Change-Id: Ie1790dc0e8f49c06c7fc80d33fec0f673a9c3044 --- java/android/net/sip/SipAudioCall.java | 955 +++++++++++++++++++++++++---- java/android/net/sip/SipAudioCallImpl.java | 766 ----------------------- java/android/net/sip/SipManager.java | 169 ++--- java/android/net/sip/SipProfile.java | 5 +- java/android/net/sip/SipSession.java | 531 ++++++++++++++++ java/android/net/sip/SipSessionState.java | 94 --- 6 files changed, 1459 insertions(+), 1061 deletions(-) delete mode 100644 java/android/net/sip/SipAudioCallImpl.java create mode 100644 java/android/net/sip/SipSession.java delete mode 100644 java/android/net/sip/SipSessionState.java diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 0069fe0..2f4fd90 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/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/java/android/net/sip/SipAudioCallImpl.java b/java/android/net/sip/SipAudioCallImpl.java deleted file mode 100644 index 2f8d175..0000000 --- a/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/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 31768d7..5976a04 100644 --- a/java/android/net/sip/SipManager.java +++ b/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: *
    - *
  • register a {@link SipProfile} to have the background SIP service listen - * to incoming calls and broadcast them with registered command string. See + *
  • 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}.
  • *
  • make/take SIP-based audio calls. See * {@link #makeAudioCall} and {@link #takeAudioCall}.
  • - *
  • register/unregister with a SIP service provider. See + *
  • register/unregister with a SIP service provider manually. See * {@link #register} and {@link #unregister}.
  • - *
  • process SIP events directly with a {@link ISipSession} created by + *
  • process SIP events directly with a {@link SipSession} created by * {@link #createSipSession}.
  • *
* @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/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 88bfba9..6d5cb3c 100644 --- a/java/android/net/sip/SipProfile.java +++ b/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 CREATOR = new Parcelable.Creator() { 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/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java new file mode 100644 index 0000000..0cc7206 --- /dev/null +++ b/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/java/android/net/sip/SipSessionState.java b/java/android/net/sip/SipSessionState.java deleted file mode 100644 index 31e9d3f..0000000 --- a/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() { - } -} -- cgit v1.2.3 From 6b6bdc61087ff14aa26ba461daf3d5bb9922ea9c Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 23 Sep 2010 17:11:02 +0800 Subject: SDP: remove dead code. Change-Id: I2a5764a2b9cabc54b0ac18666e494c1cb39c4e9b --- java/android/net/sip/SdpSessionDescription.java | 428 ------------------------ java/android/net/sip/SessionDescription.aidl | 19 -- java/android/net/sip/SessionDescription.java | 83 ----- 3 files changed, 530 deletions(-) delete mode 100644 java/android/net/sip/SdpSessionDescription.java delete mode 100644 java/android/net/sip/SessionDescription.aidl delete mode 100644 java/android/net/sip/SessionDescription.java diff --git a/java/android/net/sip/SdpSessionDescription.java b/java/android/net/sip/SdpSessionDescription.java deleted file mode 100644 index f6ae837..0000000 --- a/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 RFC 4566. - * @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 typeVector = new Vector(); - 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 descriptions = (Vector) - 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 v = (Vector) - 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 getAudioCodecs() { - MediaDescription md = getMediaDescription(AUDIO); - if (md == null) return new ArrayList(); - - // 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 codecs = new ArrayList(); - Vector v = (Vector) - 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 descriptions = (Vector) - 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/java/android/net/sip/SessionDescription.aidl b/java/android/net/sip/SessionDescription.aidl deleted file mode 100644 index a120d16..0000000 --- a/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/java/android/net/sip/SessionDescription.java b/java/android/net/sip/SessionDescription.java deleted file mode 100644 index d476f0b..0000000 --- a/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 CREATOR = - new Parcelable.Creator() { - 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; - } - } -} -- cgit v1.2.3 From e331636da9d412235a6f7037addc6b516dc9876a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 24 Sep 2010 14:23:31 +0800 Subject: fix build Change-Id: Iff05b5ea7f535f532eec2af1edf78fdf8acfa21c --- java/android/net/sip/SipAudioCall.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 2f4fd90..2135fcb 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -721,6 +721,7 @@ public class SipAudioCall extends SipSessionAdapter { } private void grabWifiHighPerfLock() { + /* not available in master yet if (mWifiHighPerfLock == null) { Log.v(TAG, "acquire wifi high perf lock"); mWifiHighPerfLock = ((WifiManager) @@ -728,6 +729,7 @@ public class SipAudioCall extends SipSessionAdapter { .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); mWifiHighPerfLock.acquire(); } + */ } private void releaseWifiHighPerfLock() { -- cgit v1.2.3 From 7a4ec7268fbfb56e22f1cb8497d37ed733d8aa8e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 24 Sep 2010 23:27:40 +0800 Subject: SipAudioCall: remove SipManager dependency. Change-Id: I2dc8bf427e52f64529ee0e0261362b975a8917c6 --- java/android/net/sip/SipAudioCall.java | 16 ++++++---------- java/android/net/sip/SipManager.java | 7 ++++++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 2f4fd90..c23da20 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -46,7 +46,7 @@ import java.util.Map; * Class that handles an audio call over SIP. */ /** @hide */ -public class SipAudioCall extends SipSessionAdapter { +public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); private static final boolean RELEASE_SOCKET = true; private static final boolean DONT_RELEASE_SOCKET = false; @@ -530,7 +530,7 @@ public class SipAudioCall extends SipSessionAdapter { * 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 sipSession the {@link SipSession} for carrying out the call * @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 @@ -538,16 +538,12 @@ public class SipAudioCall extends SipSessionAdapter { * call */ 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?"); - } + SipSession sipSession, int timeout) throws SipException { + mSipSession = sipSession; try { mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); - s.makeCall(peerProfile, createOffer().encode(), timeout); + sipSession.setListener(createListener()); + sipSession.makeCall(peerProfile, createOffer().encode(), timeout); } catch (IOException e) { throw new SipException("makeCall()", e); } diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 5976a04..59631c1 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -269,7 +269,12 @@ public class SipManager { throws SipException { SipAudioCall call = new SipAudioCall(mContext, localProfile); call.setListener(listener); - call.makeCall(peerProfile, this, timeout); + SipSession s = createSipSession(localProfile, null); + if (s == null) { + throw new SipException( + "Failed to create SipSession; network available?"); + } + call.makeCall(peerProfile, s, timeout); return call; } -- cgit v1.2.3 From 64d2d433d0f4d37fccf2d98109d22f768c71f0dc Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 23 Sep 2010 23:23:11 +0800 Subject: Fix the unhold issue especially if one is behind NAT. +call startAudio() when call is established. Change-Id: Ib6a1e34017fb83007ce275da1991058e8b803833 --- jni/rtp/AudioGroup.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 7cf0613..81d4dfc 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -86,6 +86,8 @@ public: void decode(int tick); private: + bool isNatAddress(struct sockaddr_storage *addr); + enum { NORMAL = 0, SEND_ONLY = 1, @@ -316,6 +318,16 @@ void AudioStream::encode(int tick, AudioStream *chain) sizeof(mRemote)); } +bool AudioStream::isNatAddress(struct sockaddr_storage *addr) { + if (addr->ss_family != AF_INET) return false; + struct sockaddr_in *s4 = (struct sockaddr_in *)addr; + unsigned char *d = (unsigned char *) &s4->sin_addr; + if ((d[0] == 10) + || ((d[0] == 172) && (d[1] & 0x10)) + || ((d[0] == 192) && (d[1] == 168))) return true; + return false; +} + void AudioStream::decode(int tick) { char c; @@ -363,8 +375,21 @@ void AudioStream::decode(int tick) MSG_TRUNC | MSG_DONTWAIT) >> 1; } else { __attribute__((aligned(4))) uint8_t buffer[2048]; - length = recv(mSocket, buffer, sizeof(buffer), - MSG_TRUNC | MSG_DONTWAIT); + struct sockaddr_storage src_addr; + socklen_t addrlen; + length = recvfrom(mSocket, buffer, sizeof(buffer), + MSG_TRUNC|MSG_DONTWAIT, (sockaddr*)&src_addr, &addrlen); + + // The following if clause is for fixing the target address if + // proxy server did not replace the NAT address with its media + // port in SDP. Although it is proxy server's responsibility for + // replacing the connection address with correct one, we will change + // the target address as we detect the difference for now until we + // know the best way to get rid of this issue. + if ((memcmp((void*)&src_addr, (void*)&mRemote, addrlen) != 0) && + isNatAddress(&mRemote)) { + memcpy((void*)&mRemote, (void*)&src_addr, addrlen); + } // Do we need to check SSRC, sequence, and timestamp? They are not // reliable but at least they can be used to identify duplicates? -- cgit v1.2.3 From c87f1e015123d985a37e8ab276e124b62068e793 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sat, 25 Sep 2010 23:21:23 +0800 Subject: SipService: handle cross-domain authentication error and add new CROSS_DOMAIN_AUTHENTICATION error code and OUT_OF_NETWORK DisconnectCause. http://b/issue?id=3020185 Change-Id: Icc0a341599d5a72b7cb2d43675fbddc516544978 --- java/android/net/sip/SipErrorCode.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index 7496d28..eb7a1ae 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -58,6 +58,9 @@ public class SipErrorCode { /** When data connection is lost. */ public static final int DATA_CONNECTION_LOST = -10; + /** Cross-domain authentication required. */ + public static final int CROSS_DOMAIN_AUTHENTICATION = -11; + public static String toString(int errorCode) { switch (errorCode) { case NO_ERROR: @@ -82,6 +85,8 @@ public class SipErrorCode { return "IN_PROGRESS"; case DATA_CONNECTION_LOST: return "DATA_CONNECTION_LOST"; + case CROSS_DOMAIN_AUTHENTICATION: + return "CROSS_DOMAIN_AUTHENTICATION"; default: return "UNKNOWN"; } -- cgit v1.2.3 From 9c1fbe7bca34ac7463079926a401a3ce42717460 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sat, 25 Sep 2010 22:49:59 +0800 Subject: Move SipService out of SystemServer to phone process. Companion CL: https://android-git/g/#change,70187 http://b/issue?id=2998069 Change-Id: I90923ac522ef363a4e04292f652d413c5a1526ad --- java/com/android/server/sip/SipHelper.java | 464 +++++++ java/com/android/server/sip/SipService.java | 1234 +++++++++++++++++ java/com/android/server/sip/SipSessionGroup.java | 1393 ++++++++++++++++++++ .../server/sip/SipSessionListenerProxy.java | 217 +++ 4 files changed, 3308 insertions(+) create mode 100644 java/com/android/server/sip/SipHelper.java create mode 100644 java/com/android/server/sip/SipService.java create mode 100644 java/com/android/server/sip/SipSessionGroup.java create mode 100644 java/com/android/server/sip/SipSessionListenerProxy.java diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java new file mode 100644 index 0000000..050eddc --- /dev/null +++ b/java/com/android/server/sip/SipHelper.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import gov.nist.javax.sip.SipStackExt; +import gov.nist.javax.sip.clientauthutils.AccountManager; +import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; + +import android.net.sip.SipProfile; +import android.util.Log; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; +import javax.sip.ClientTransaction; +import javax.sip.Dialog; +import javax.sip.DialogTerminatedEvent; +import javax.sip.InvalidArgumentException; +import javax.sip.ListeningPoint; +import javax.sip.PeerUnavailableException; +import javax.sip.RequestEvent; +import javax.sip.ResponseEvent; +import javax.sip.ServerTransaction; +import javax.sip.SipException; +import javax.sip.SipFactory; +import javax.sip.SipProvider; +import javax.sip.SipStack; +import javax.sip.Transaction; +import javax.sip.TransactionAlreadyExistsException; +import javax.sip.TransactionTerminatedEvent; +import javax.sip.TransactionUnavailableException; +import javax.sip.TransactionState; +import javax.sip.address.Address; +import javax.sip.address.AddressFactory; +import javax.sip.address.SipURI; +import javax.sip.header.CSeqHeader; +import javax.sip.header.CallIdHeader; +import javax.sip.header.ContactHeader; +import javax.sip.header.FromHeader; +import javax.sip.header.Header; +import javax.sip.header.HeaderFactory; +import javax.sip.header.MaxForwardsHeader; +import javax.sip.header.ToHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Message; +import javax.sip.message.MessageFactory; +import javax.sip.message.Request; +import javax.sip.message.Response; + +/** + * Helper class for holding SIP stack related classes and for various low-level + * SIP tasks like sending messages. + */ +class SipHelper { + private static final String TAG = SipHelper.class.getSimpleName(); + private static final boolean DEBUG = true; + + private SipStack mSipStack; + private SipProvider mSipProvider; + private AddressFactory mAddressFactory; + private HeaderFactory mHeaderFactory; + private MessageFactory mMessageFactory; + + public SipHelper(SipStack sipStack, SipProvider sipProvider) + throws PeerUnavailableException { + mSipStack = sipStack; + mSipProvider = sipProvider; + + SipFactory sipFactory = SipFactory.getInstance(); + mAddressFactory = sipFactory.createAddressFactory(); + mHeaderFactory = sipFactory.createHeaderFactory(); + mMessageFactory = sipFactory.createMessageFactory(); + } + + private FromHeader createFromHeader(SipProfile profile, String tag) + throws ParseException { + return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); + } + + private ToHeader createToHeader(SipProfile profile) throws ParseException { + return createToHeader(profile, null); + } + + private ToHeader createToHeader(SipProfile profile, String tag) + throws ParseException { + return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); + } + + private CallIdHeader createCallIdHeader() { + return mSipProvider.getNewCallId(); + } + + private CSeqHeader createCSeqHeader(String method) + throws ParseException, InvalidArgumentException { + long sequence = (long) (Math.random() * 10000); + return mHeaderFactory.createCSeqHeader(sequence, method); + } + + private MaxForwardsHeader createMaxForwardsHeader() + throws InvalidArgumentException { + return mHeaderFactory.createMaxForwardsHeader(70); + } + + private MaxForwardsHeader createMaxForwardsHeader(int max) + throws InvalidArgumentException { + return mHeaderFactory.createMaxForwardsHeader(max); + } + + private ListeningPoint getListeningPoint() throws SipException { + ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); + if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); + if (lp == null) { + ListeningPoint[] lps = mSipProvider.getListeningPoints(); + if ((lps != null) && (lps.length > 0)) lp = lps[0]; + } + if (lp == null) { + throw new SipException("no listening point is available"); + } + return lp; + } + + private List createViaHeaders() + throws ParseException, SipException { + List viaHeaders = new ArrayList(1); + ListeningPoint lp = getListeningPoint(); + ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), + lp.getPort(), lp.getTransport(), null); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + return viaHeaders; + } + + private ContactHeader createContactHeader(SipProfile profile) + throws ParseException, SipException { + ListeningPoint lp = getListeningPoint(); + SipURI contactURI = + createSipUri(profile.getUserName(), profile.getProtocol(), lp); + + Address contactAddress = mAddressFactory.createAddress(contactURI); + contactAddress.setDisplayName(profile.getDisplayName()); + + return mHeaderFactory.createContactHeader(contactAddress); + } + + private ContactHeader createWildcardContactHeader() { + ContactHeader contactHeader = mHeaderFactory.createContactHeader(); + contactHeader.setWildCard(); + return contactHeader; + } + + private SipURI createSipUri(String username, String transport, + ListeningPoint lp) throws ParseException { + SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress()); + try { + uri.setPort(lp.getPort()); + uri.setTransportParam(transport); + } catch (InvalidArgumentException e) { + throw new RuntimeException(e); + } + return uri; + } + + public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) + throws SipException { + try { + Request request = createRequest(Request.OPTIONS, userProfile, tag); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (Exception e) { + throw new SipException("sendKeepAlive()", e); + } + } + + public ClientTransaction sendRegister(SipProfile userProfile, String tag, + int expiry) throws SipException { + try { + Request request = createRequest(Request.REGISTER, userProfile, tag); + if (expiry == 0) { + // remove all previous registrations by wildcard + // rfc3261#section-10.2.2 + request.addHeader(createWildcardContactHeader()); + } else { + request.addHeader(createContactHeader(userProfile)); + } + request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendRegister()", e); + } + } + + private Request createRequest(String requestType, SipProfile userProfile, + String tag) throws ParseException, SipException { + FromHeader fromHeader = createFromHeader(userProfile, tag); + ToHeader toHeader = createToHeader(userProfile); + SipURI requestURI = mAddressFactory.createSipURI("sip:" + + userProfile.getSipDomain()); + List viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(requestType); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + Request request = mMessageFactory.createRequest(requestURI, + requestType, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", + "SIPAUA/0.1.001"); + request.addHeader(userAgentHeader); + return request; + } + + public ClientTransaction handleChallenge(ResponseEvent responseEvent, + AccountManager accountManager) throws SipException { + AuthenticationHelper authenticationHelper = + ((SipStackExt) mSipStack).getAuthenticationHelper( + accountManager, mHeaderFactory); + ClientTransaction tid = responseEvent.getClientTransaction(); + ClientTransaction ct = authenticationHelper.handleChallenge( + responseEvent.getResponse(), tid, mSipProvider, 5); + ct.sendRequest(); + return ct; + } + + public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, + String sessionDescription, String tag) + throws SipException { + try { + FromHeader fromHeader = createFromHeader(caller, tag); + ToHeader toHeader = createToHeader(callee); + SipURI requestURI = callee.getUri(); + List viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + + Request request = mMessageFactory.createRequest(requestURI, + Request.INVITE, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + + request.addHeader(createContactHeader(caller)); + request.setContent(sessionDescription, + mHeaderFactory.createContentTypeHeader( + "application", "sdp")); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + if (DEBUG) Log.d(TAG, "send INVITE: " + request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendInvite()", e); + } + } + + public ClientTransaction sendReinvite(Dialog dialog, + String sessionDescription) throws SipException { + try { + Request request = dialog.createRequest(Request.INVITE); + request.setContent(sessionDescription, + mHeaderFactory.createContentTypeHeader( + "application", "sdp")); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request); + dialog.sendRequest(clientTransaction); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendReinvite()", e); + } + } + + private ServerTransaction getServerTransaction(RequestEvent event) + throws SipException { + ServerTransaction transaction = event.getServerTransaction(); + if (transaction == null) { + Request request = event.getRequest(); + return mSipProvider.getNewServerTransaction(request); + } else { + return transaction; + } + } + + /** + * @param event the INVITE request event + */ + public ServerTransaction sendRinging(RequestEvent event, String tag) + throws SipException { + try { + Request request = event.getRequest(); + ServerTransaction transaction = getServerTransaction(event); + + Response response = mMessageFactory.createResponse(Response.RINGING, + request); + + ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); + toHeader.setTag(tag); + response.addHeader(toHeader); + if (DEBUG) Log.d(TAG, "send RINGING: " + response); + transaction.sendResponse(response); + return transaction; + } catch (ParseException e) { + throw new SipException("sendRinging()", e); + } + } + + /** + * @param event the INVITE request event + */ + public ServerTransaction sendInviteOk(RequestEvent event, + SipProfile localProfile, String sessionDescription, + ServerTransaction inviteTransaction) + throws SipException { + try { + Request request = event.getRequest(); + Response response = mMessageFactory.createResponse(Response.OK, + request); + response.addHeader(createContactHeader(localProfile)); + response.setContent(sessionDescription, + mHeaderFactory.createContentTypeHeader( + "application", "sdp")); + + if (inviteTransaction == null) { + inviteTransaction = getServerTransaction(event); + } + + if (inviteTransaction.getState() != TransactionState.COMPLETED) { + if (DEBUG) Log.d(TAG, "send OK: " + response); + inviteTransaction.sendResponse(response); + } + + return inviteTransaction; + } catch (ParseException e) { + throw new SipException("sendInviteOk()", e); + } + } + + public void sendInviteBusyHere(RequestEvent event, + ServerTransaction inviteTransaction) throws SipException { + try { + Request request = event.getRequest(); + Response response = mMessageFactory.createResponse( + Response.BUSY_HERE, request); + + if (inviteTransaction.getState() != TransactionState.COMPLETED) { + if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response); + inviteTransaction.sendResponse(response); + } + } catch (ParseException e) { + throw new SipException("sendInviteBusyHere()", e); + } + } + + /** + * @param event the INVITE ACK request event + */ + public void sendInviteAck(ResponseEvent event, Dialog dialog) + throws SipException { + Response response = event.getResponse(); + long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) + .getSeqNumber(); + Request ack = dialog.createAck(cseq); + if (DEBUG) Log.d(TAG, "send ACK: " + ack); + dialog.sendAck(ack); + } + + public void sendBye(Dialog dialog) throws SipException { + Request byeRequest = dialog.createRequest(Request.BYE); + if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest); + dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); + } + + public void sendCancel(ClientTransaction inviteTransaction) + throws SipException { + Request cancelRequest = inviteTransaction.createCancel(); + if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest); + mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); + } + + public void sendResponse(RequestEvent event, int responseCode) + throws SipException { + try { + Response response = mMessageFactory.createResponse( + responseCode, event.getRequest()); + if (DEBUG) Log.d(TAG, "send response: " + response); + getServerTransaction(event).sendResponse(response); + } catch (ParseException e) { + throw new SipException("sendResponse()", e); + } + } + + public void sendInviteRequestTerminated(Request inviteRequest, + ServerTransaction inviteTransaction) throws SipException { + try { + Response response = mMessageFactory.createResponse( + Response.REQUEST_TERMINATED, inviteRequest); + if (DEBUG) Log.d(TAG, "send response: " + response); + inviteTransaction.sendResponse(response); + } catch (ParseException e) { + throw new SipException("sendInviteRequestTerminated()", e); + } + } + + public static String getCallId(EventObject event) { + if (event == null) return null; + if (event instanceof RequestEvent) { + return getCallId(((RequestEvent) event).getRequest()); + } else if (event instanceof ResponseEvent) { + return getCallId(((ResponseEvent) event).getResponse()); + } else if (event instanceof DialogTerminatedEvent) { + Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); + return getCallId(((DialogTerminatedEvent) event).getDialog()); + } else if (event instanceof TransactionTerminatedEvent) { + TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; + return getCallId(e.isServerTransaction() + ? e.getServerTransaction() + : e.getClientTransaction()); + } else { + Object source = event.getSource(); + if (source instanceof Transaction) { + return getCallId(((Transaction) source)); + } else if (source instanceof Dialog) { + return getCallId((Dialog) source); + } + } + return ""; + } + + public static String getCallId(Transaction transaction) { + return ((transaction != null) ? getCallId(transaction.getRequest()) + : ""); + } + + private static String getCallId(Message message) { + CallIdHeader callIdHeader = + (CallIdHeader) message.getHeader(CallIdHeader.NAME); + return callIdHeader.getCallId(); + } + + private static String getCallId(Dialog dialog) { + return dialog.getCallId().getCallId(); + } +} diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java new file mode 100644 index 0000000..0ff5586 --- /dev/null +++ b/java/com/android/server/sip/SipService.java @@ -0,0 +1,1234 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.sip.ISipService; +import android.net.sip.ISipSession; +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.wifi.WifiManager; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeSet; +import javax.sip.SipException; + +/** + * @hide + */ +public final class SipService extends ISipService.Stub { + private static final String TAG = "SipService"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_TIMER = DEBUG && false; + private static final int EXPIRY_TIME = 3600; + private static final int SHORT_EXPIRY_TIME = 10; + private static final int MIN_EXPIRY_TIME = 60; + + private Context mContext; + private String mLocalIp; + private String mNetworkType; + private boolean mConnected; + private WakeupTimer mTimer; + private WifiManager.WifiLock mWifiLock; + private boolean mWifiOnly; + + private MyExecutor mExecutor; + + // SipProfile URI --> group + private Map mSipGroups = + new HashMap(); + + // session ID --> session + private Map mPendingSessions = + new HashMap(); + + private ConnectivityReceiver mConnectivityReceiver; + + /** + * Starts the SIP service. Do nothing if the SIP API is not supported on the + * device. + */ + public static void start(Context context) { + if (SipManager.isApiSupported(context)) { + ServiceManager.addService("sip", new SipService(context)); + Log.i(TAG, "SIP service started"); + } + } + + private SipService(Context context) { + if (DEBUG) Log.d(TAG, " service started!"); + mContext = context; + mConnectivityReceiver = new ConnectivityReceiver(); + context.registerReceiver(mConnectivityReceiver, + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + + mTimer = new WakeupTimer(context); + mWifiOnly = SipManager.isSipWifiOnly(context); + } + + private MyExecutor getExecutor() { + // create mExecutor lazily + if (mExecutor == null) mExecutor = new MyExecutor(); + return mExecutor; + } + + public synchronized SipProfile[] getListOfProfiles() { + SipProfile[] profiles = new SipProfile[mSipGroups.size()]; + int i = 0; + for (SipSessionGroupExt group : mSipGroups.values()) { + profiles[i++] = group.getLocalProfile(); + } + return profiles; + } + + public void open(SipProfile localProfile) { + localProfile.setCallingUid(Binder.getCallingUid()); + if (localProfile.getAutoRegistration()) { + openToReceiveCalls(localProfile); + } else { + openToMakeCalls(localProfile); + } + } + + private void openToMakeCalls(SipProfile localProfile) { + try { + createGroup(localProfile); + } catch (SipException e) { + Log.e(TAG, "openToMakeCalls()", e); + // TODO: how to send the exception back + } + } + + private void openToReceiveCalls(SipProfile localProfile) { + open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null); + } + + public synchronized void open3(SipProfile localProfile, + String incomingCallBroadcastAction, ISipSessionListener listener) { + localProfile.setCallingUid(Binder.getCallingUid()); + if (TextUtils.isEmpty(incomingCallBroadcastAction)) { + throw new RuntimeException( + "empty broadcast action for incoming call"); + } + if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + + incomingCallBroadcastAction + ": " + listener); + try { + SipSessionGroupExt group = createGroup(localProfile, + incomingCallBroadcastAction, listener); + if (localProfile.getAutoRegistration()) { + group.openToReceiveCalls(); + if (isWifiOn()) grabWifiLock(); + } + } catch (SipException e) { + Log.e(TAG, "openToReceiveCalls()", e); + // TODO: how to send the exception back + } + } + + public synchronized void close(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.remove(localProfileUri); + if (group != null) { + notifyProfileRemoved(group.getLocalProfile()); + group.close(); + if (isWifiOn() && !anyOpened()) releaseWifiLock(); + } + } + + public synchronized boolean isOpened(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + return ((group != null) ? group.isOpened() : false); + } + + public synchronized boolean isRegistered(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + return ((group != null) ? group.isRegistered() : false); + } + + public synchronized void setRegistrationListener(String localProfileUri, + ISipSessionListener listener) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + if (group != null) group.setListener(listener); + } + + public synchronized ISipSession createSession(SipProfile localProfile, + ISipSessionListener listener) { + localProfile.setCallingUid(Binder.getCallingUid()); + if (!mConnected) return null; + try { + SipSessionGroupExt group = createGroup(localProfile); + return group.createSession(listener); + } catch (SipException e) { + Log.w(TAG, "createSession()", e); + return null; + } + } + + public synchronized ISipSession getPendingSession(String callId) { + if (callId == null) return null; + return mPendingSessions.get(callId); + } + + private String determineLocalIp() { + try { + DatagramSocket s = new DatagramSocket(); + s.connect(InetAddress.getByName("192.168.1.1"), 80); + return s.getLocalAddress().getHostAddress(); + } catch (IOException e) { + Log.w(TAG, "determineLocalIp()", e); + // dont do anything; there should be a connectivity change going + return null; + } + } + + private SipSessionGroupExt createGroup(SipProfile localProfile) + throws SipException { + String key = localProfile.getUriString(); + SipSessionGroupExt group = mSipGroups.get(key); + if (group == null) { + group = new SipSessionGroupExt(localProfile, null, null); + mSipGroups.put(key, group); + notifyProfileAdded(localProfile); + } + return group; + } + + private SipSessionGroupExt createGroup(SipProfile localProfile, + String incomingCallBroadcastAction, ISipSessionListener listener) + throws SipException { + String key = localProfile.getUriString(); + SipSessionGroupExt group = mSipGroups.get(key); + if (group != null) { + group.setIncomingCallBroadcastAction( + incomingCallBroadcastAction); + group.setListener(listener); + } else { + group = new SipSessionGroupExt(localProfile, + incomingCallBroadcastAction, listener); + mSipGroups.put(key, group); + notifyProfileAdded(localProfile); + } + return group; + } + + private void notifyProfileAdded(SipProfile localProfile) { + if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); + 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.ACTION_SIP_REMOVE_PHONE); + intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); + mContext.sendBroadcast(intent); + } + + private boolean anyOpened() { + for (SipSessionGroupExt group : mSipGroups.values()) { + if (group.isOpened()) return true; + } + return false; + } + + private void grabWifiLock() { + if (mWifiLock == null) { + if (DEBUG) Log.d(TAG, "acquire wifi lock"); + mWifiLock = ((WifiManager) + mContext.getSystemService(Context.WIFI_SERVICE)) + .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); + mWifiLock.acquire(); + } + } + + private void releaseWifiLock() { + if (mWifiLock != null) { + if (DEBUG) Log.d(TAG, "release wifi lock"); + mWifiLock.release(); + mWifiLock = null; + } + } + + private boolean isWifiOn() { + return "WIFI".equalsIgnoreCase(mNetworkType); + //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType)); + } + + private synchronized void onConnectivityChanged( + String type, boolean connected) { + if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " + + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") + + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); + + boolean sameType = type.equals(mNetworkType); + if (!sameType && !connected) return; + + boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); + boolean isWifi = "WIFI".equalsIgnoreCase(type); + boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); + boolean wifiOn = isWifi && connected; + if (wifiOff) { + releaseWifiLock(); + } else if (wifiOn) { + if (anyOpened()) grabWifiLock(); + } + + try { + boolean wasConnected = mConnected; + mNetworkType = type; + mConnected = connected; + + if (wasConnected) { + mLocalIp = null; + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(false); + } + } + + if (connected) { + mLocalIp = determineLocalIp(); + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(true); + } + } + + } catch (SipException e) { + Log.e(TAG, "onConnectivityChanged()", e); + } + } + + private synchronized void addPendingSession(ISipSession session) { + try { + mPendingSessions.put(session.getCallId(), session); + } catch (RemoteException e) { + // should not happen with a local call + Log.e(TAG, "addPendingSession()", e); + } + } + + private class SipSessionGroupExt extends SipSessionAdapter { + private SipSessionGroup mSipGroup; + private String mIncomingCallBroadcastAction; + private boolean mOpened; + + private AutoRegistrationProcess mAutoRegistration = + new AutoRegistrationProcess(); + + public SipSessionGroupExt(SipProfile localProfile, + String incomingCallBroadcastAction, + ISipSessionListener listener) throws SipException { + String password = localProfile.getPassword(); + SipProfile p = duplicate(localProfile); + mSipGroup = createSipSessionGroup(mLocalIp, p, password); + mIncomingCallBroadcastAction = incomingCallBroadcastAction; + mAutoRegistration.setListener(listener); + } + + public SipProfile getLocalProfile() { + return mSipGroup.getLocalProfile(); + } + + // network connectivity is tricky because network can be disconnected + // at any instant so need to deal with exceptions carefully even when + // you think you are connected + private SipSessionGroup createSipSessionGroup(String localIp, + SipProfile localProfile, String password) throws SipException { + try { + return new SipSessionGroup(localIp, localProfile, password); + } catch (IOException e) { + // network disconnected + Log.w(TAG, "createSipSessionGroup(): network disconnected?"); + if (localIp != null) { + return createSipSessionGroup(null, localProfile, password); + } else { + // recursive + Log.wtf(TAG, "impossible!"); + throw new RuntimeException("createSipSessionGroup"); + } + } + } + + private SipProfile duplicate(SipProfile p) { + try { + return new SipProfile.Builder(p).setPassword("*").build(); + } catch (Exception e) { + Log.wtf(TAG, "duplicate()", e); + throw new RuntimeException("duplicate profile", e); + } + } + + public void setListener(ISipSessionListener listener) { + mAutoRegistration.setListener(listener); + } + + public void setIncomingCallBroadcastAction(String action) { + mIncomingCallBroadcastAction = action; + } + + public void openToReceiveCalls() throws SipException { + mOpened = true; + if (mConnected) { + mSipGroup.openToReceiveCalls(this); + mAutoRegistration.start(mSipGroup); + } + if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " + + mIncomingCallBroadcastAction); + } + + public void onConnectivityChanged(boolean connected) + throws SipException { + mSipGroup.onConnectivityChanged(); + if (connected) { + resetGroup(mLocalIp); + if (mOpened) openToReceiveCalls(); + } else { + // close mSipGroup but remember mOpened + if (DEBUG) Log.d(TAG, " close auto reg temporarily: " + + getUri() + ": " + mIncomingCallBroadcastAction); + mSipGroup.close(); + mAutoRegistration.stop(); + } + } + + private void resetGroup(String localIp) throws SipException { + try { + mSipGroup.reset(localIp); + } catch (IOException e) { + // network disconnected + Log.w(TAG, "resetGroup(): network disconnected?"); + if (localIp != null) { + resetGroup(null); // reset w/o local IP + } else { + // recursive + Log.wtf(TAG, "impossible!"); + throw new RuntimeException("resetGroup"); + } + } + } + + public void close() { + mOpened = false; + mSipGroup.close(); + mAutoRegistration.stop(); + if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " + + mIncomingCallBroadcastAction); + } + + public ISipSession createSession(ISipSessionListener listener) { + return mSipGroup.createSession(listener); + } + + @Override + public void onRinging(ISipSession session, SipProfile caller, + String sessionDescription) { + synchronized (SipService.this) { + try { + if (!isRegistered()) { + session.endCall(); + return; + } + + // send out incoming call broadcast + addPendingSession(session); + Intent intent = SipManager.createIncomingCallBroadcast( + session.getCallId(), sessionDescription) + .setAction(mIncomingCallBroadcastAction); + if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " + + caller.getUri() + ": " + session.getCallId() + + " " + mIncomingCallBroadcastAction); + mContext.sendBroadcast(intent); + } catch (RemoteException e) { + // should never happen with a local call + Log.e(TAG, "processCall()", e); + } + } + } + + @Override + public void onError(ISipSession session, int errorCode, + String message) { + if (DEBUG) Log.d(TAG, "sip session error: " + + SipErrorCode.toString(errorCode) + ": " + message); + } + + public boolean isOpened() { + return mOpened; + } + + public boolean isRegistered() { + return mAutoRegistration.isRegistered(); + } + + private String getUri() { + return mSipGroup.getLocalProfileUri(); + } + } + + private class KeepAliveProcess implements Runnable { + private static final String TAG = "\\KEEPALIVE/"; + private static final int INTERVAL = 10; + private SipSessionGroup.SipSessionImpl mSession; + + public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { + mSession = session; + } + + public void start() { + mTimer.set(INTERVAL * 1000, this); + } + + public void run() { + // delegate to mExecutor + getExecutor().addTask(new Runnable() { + public void run() { + realRun(); + } + }); + } + + private void realRun() { + synchronized (SipService.this) { + SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + if (DEBUG) Log.d(TAG, "~~~ keepalive"); + mTimer.cancel(this); + session.sendKeepAlive(); + if (session.isReRegisterRequired()) { + mSession.register(EXPIRY_TIME); + } else { + mTimer.set(INTERVAL * 1000, this); + } + } + } + + public void stop() { + mTimer.cancel(this); + } + } + + private class AutoRegistrationProcess extends SipSessionAdapter + implements Runnable { + private SipSessionGroup.SipSessionImpl mSession; + private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); + private KeepAliveProcess mKeepAliveProcess; + private int mBackoff = 1; + private boolean mRegistered; + private long mExpiryTime; + private int mErrorCode; + private String mErrorMessage; + + private String getAction() { + return toString(); + } + + public void start(SipSessionGroup group) { + if (mSession == null) { + mBackoff = 1; + mSession = (SipSessionGroup.SipSessionImpl) + group.createSession(this); + // return right away if no active network connection. + if (mSession == null) return; + + // start unregistration to clear up old registration at server + // TODO: when rfc5626 is deployed, use reg-id and sip.instance + // in registration to avoid adding duplicate entries to server + mSession.unregister(); + if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " + + mSession.getLocalProfile().getUriString()); + } + } + + public void stop() { + stop(false); + } + + private void stopButKeepStates() { + stop(true); + } + + private void stop(boolean keepStates) { + if (mSession == null) return; + if (mConnected && mRegistered) mSession.unregister(); + mTimer.cancel(this); + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + if (!keepStates) { + mSession = null; + mRegistered = false; + } + } + + private boolean isStopped() { + return (mSession == null); + } + + public void setListener(ISipSessionListener listener) { + synchronized (SipService.this) { + mProxy.setListener(listener); + if (mSession == null) return; + + try { + int state = (mSession == null) + ? SipSession.State.READY_TO_CALL + : mSession.getState(); + if ((state == SipSession.State.REGISTERING) + || (state == SipSession.State.DEREGISTERING)) { + mProxy.onRegistering(mSession); + } else if (mRegistered) { + int duration = (int) + (mExpiryTime - SystemClock.elapsedRealtime()); + mProxy.onRegistrationDone(mSession, duration); + } else if (mErrorCode != SipErrorCode.NO_ERROR) { + if (mErrorCode == SipErrorCode.TIME_OUT) { + mProxy.onRegistrationTimeout(mSession); + } else { + mProxy.onRegistrationFailed(mSession, mErrorCode, + mErrorMessage); + } + } + } catch (Throwable t) { + Log.w(TAG, "setListener(): " + t); + } + } + } + + public boolean isRegistered() { + return mRegistered; + } + + public void run() { + // delegate to mExecutor + getExecutor().addTask(new Runnable() { + public void run() { + realRun(); + } + }); + } + + private void realRun() { + mErrorCode = SipErrorCode.NO_ERROR; + mErrorMessage = null; + if (DEBUG) Log.d(TAG, "~~~ registering"); + synchronized (SipService.this) { + if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); + } + } + + private boolean isBehindNAT(String address) { + try { + byte[] d = InetAddress.getByName(address).getAddress(); + if ((d[0] == 10) || + (((0x000000FF & ((int)d[0])) == 172) && + ((0x000000F0 & ((int)d[1])) == 16)) || + (((0x000000FF & ((int)d[0])) == 192) && + ((0x000000FF & ((int)d[1])) == 168))) { + return true; + } + } catch (UnknownHostException e) { + Log.e(TAG, "isBehindAT()" + address, e); + } + return false; + } + + private void restart(int duration) { + if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); + mTimer.cancel(this); + mTimer.set(duration * 1000, this); + } + + private int backoffDuration() { + int duration = SHORT_EXPIRY_TIME * mBackoff; + if (duration > 3600) { + duration = 3600; + } else { + mBackoff *= 2; + } + return duration; + } + + @Override + public void onRegistering(ISipSession session) { + if (DEBUG) Log.d(TAG, "onRegistering(): " + session); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + mRegistered = false; + mProxy.onRegistering(session); + } + } + + @Override + public void onRegistrationDone(ISipSession session, int duration) { + if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + + mProxy.onRegistrationDone(session, duration); + + if (isStopped()) return; + + if (duration > 0) { + mSession.clearReRegisterRequired(); + mExpiryTime = SystemClock.elapsedRealtime() + + (duration * 1000); + + if (!mRegistered) { + mRegistered = true; + // allow some overlap to avoid call drop during renew + duration -= MIN_EXPIRY_TIME; + if (duration < MIN_EXPIRY_TIME) { + duration = MIN_EXPIRY_TIME; + } + restart(duration); + + if (isBehindNAT(mLocalIp) || + mSession.getLocalProfile().getSendKeepAlive()) { + if (mKeepAliveProcess == null) { + mKeepAliveProcess = + new KeepAliveProcess(mSession); + } + mKeepAliveProcess.start(); + } + } + } else { + mRegistered = false; + mExpiryTime = -1L; + if (DEBUG) Log.d(TAG, "Refresh registration immediately"); + run(); + } + } + } + + @Override + public void onRegistrationFailed(ISipSession session, int errorCode, + String message) { + if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " + + SipErrorCode.toString(errorCode) + ": " + message); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + mErrorCode = errorCode; + mErrorMessage = message; + mProxy.onRegistrationFailed(session, errorCode, message); + + if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { + if (DEBUG) Log.d(TAG, " pause auto-registration"); + stopButKeepStates(); + } else if (!isStopped()) { + onError(); + } + } + } + + @Override + public void onRegistrationTimeout(ISipSession session) { + if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + mErrorCode = SipErrorCode.TIME_OUT; + mProxy.onRegistrationTimeout(session); + + if (!isStopped()) { + mRegistered = false; + onError(); + } + } + } + + private void onError() { + mRegistered = false; + restart(backoffDuration()); + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + } + } + + private class ConnectivityReceiver extends BroadcastReceiver { + private Timer mTimer = new Timer(); + private MyTimerTask mTask; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Bundle b = intent.getExtras(); + if (b != null) { + NetworkInfo netInfo = (NetworkInfo) + b.get(ConnectivityManager.EXTRA_NETWORK_INFO); + String type = netInfo.getTypeName(); + NetworkInfo.State state = netInfo.getState(); + + if (mWifiOnly && (netInfo.getType() != + ConnectivityManager.TYPE_WIFI)) { + if (DEBUG) { + Log.d(TAG, "Wifi only, other connectivity ignored: " + + type); + } + return; + } + + NetworkInfo activeNetInfo = getActiveNetworkInfo(); + if (DEBUG) { + if (activeNetInfo != null) { + Log.d(TAG, "active network: " + + activeNetInfo.getTypeName() + + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) + ? " CONNECTED" : " DISCONNECTED")); + } else { + Log.d(TAG, "active network: null"); + } + } + if ((state == NetworkInfo.State.CONNECTED) + && (activeNetInfo != null) + && (activeNetInfo.getType() != netInfo.getType())) { + if (DEBUG) Log.d(TAG, "ignore connect event: " + type + + ", active: " + activeNetInfo.getTypeName()); + return; + } + + if (state == NetworkInfo.State.CONNECTED) { + if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); + onChanged(type, true); + } else if (state == NetworkInfo.State.DISCONNECTED) { + if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); + onChanged(type, false); + } else { + if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " + + state + " " + type); + } + } + } + } + + private NetworkInfo getActiveNetworkInfo() { + ConnectivityManager cm = (ConnectivityManager) + mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.getActiveNetworkInfo(); + } + + private void onChanged(String type, boolean connected) { + synchronized (SipService.this) { + // When turning on WIFI, it needs some time for network + // connectivity to get stabile so we defer good news (because + // we want to skip the interim ones) but deliver bad news + // immediately + if (connected) { + if (mTask != null) mTask.cancel(); + mTask = new MyTimerTask(type, connected); + mTimer.schedule(mTask, 2 * 1000L); + // TODO: hold wakup lock so that we can finish change before + // the device goes to sleep + } else { + if ((mTask != null) && mTask.mNetworkType.equals(type)) { + mTask.cancel(); + } + onConnectivityChanged(type, false); + } + } + } + + private class MyTimerTask extends TimerTask { + private boolean mConnected; + private String mNetworkType; + + public MyTimerTask(String type, boolean connected) { + mNetworkType = type; + mConnected = connected; + } + + @Override + public void run() { + // delegate to mExecutor + getExecutor().addTask(new Runnable() { + public void run() { + realRun(); + } + }); + } + + private void realRun() { + synchronized (SipService.this) { + if (mTask != this) { + Log.w(TAG, " unexpected task: " + mNetworkType + + (mConnected ? " CONNECTED" : "DISCONNECTED")); + return; + } + mTask = null; + if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType + + (mConnected ? " CONNECTED" : "DISCONNECTED")); + onConnectivityChanged(mNetworkType, mConnected); + } + } + } + } + + // TODO: clean up pending SipSession(s) periodically + + + /** + * Timer that can schedule events to occur even when the device is in sleep. + * Only used internally in this package. + */ + class WakeupTimer extends BroadcastReceiver { + private static final String TAG = "_SIP.WkTimer_"; + private static final String TRIGGER_TIME = "TriggerTime"; + + private Context mContext; + private AlarmManager mAlarmManager; + + // runnable --> time to execute in SystemClock + private TreeSet mEventQueue = + new TreeSet(new MyEventComparator()); + + private PendingIntent mPendingIntent; + + public WakeupTimer(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + IntentFilter filter = new IntentFilter(getAction()); + context.registerReceiver(this, filter); + } + + /** + * Stops the timer. No event can be scheduled after this method is called. + */ + public synchronized void stop() { + mContext.unregisterReceiver(this); + if (mPendingIntent != null) { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + mEventQueue.clear(); + mEventQueue = null; + } + + private synchronized boolean stopped() { + if (mEventQueue == null) { + Log.w(TAG, "Timer stopped"); + return true; + } else { + return false; + } + } + + private void cancelAlarm() { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + + private void recalculatePeriods() { + if (mEventQueue.isEmpty()) return; + + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mMaxPeriod; + long minTriggerTime = firstEvent.mTriggerTime; + for (MyEvent e : mEventQueue) { + e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; + int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod + - minTriggerTime); + interval = interval / minPeriod * minPeriod; + e.mTriggerTime = minTriggerTime + interval; + } + TreeSet newQueue = new TreeSet( + mEventQueue.comparator()); + newQueue.addAll((Collection) mEventQueue); + mEventQueue.clear(); + mEventQueue = newQueue; + if (DEBUG_TIMER) { + Log.d(TAG, "queue re-calculated"); + printQueue(); + } + } + + // Determines the period and the trigger time of the new event and insert it + // to the queue. + private void insertEvent(MyEvent event) { + long now = SystemClock.elapsedRealtime(); + if (mEventQueue.isEmpty()) { + event.mTriggerTime = now + event.mPeriod; + mEventQueue.add(event); + return; + } + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mPeriod; + if (minPeriod <= event.mMaxPeriod) { + event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; + int interval = event.mMaxPeriod; + interval -= (int) (firstEvent.mTriggerTime - now); + interval = interval / minPeriod * minPeriod; + event.mTriggerTime = firstEvent.mTriggerTime + interval; + mEventQueue.add(event); + } else { + long triggerTime = now + event.mPeriod; + if (firstEvent.mTriggerTime < triggerTime) { + event.mTriggerTime = firstEvent.mTriggerTime; + event.mLastTriggerTime -= event.mPeriod; + } else { + event.mTriggerTime = triggerTime; + } + mEventQueue.add(event); + recalculatePeriods(); + } + } + + /** + * Sets a periodic timer. + * + * @param period the timer period; in milli-second + * @param callback is called back when the timer goes off; the same callback + * can be specified in multiple timer events + */ + public synchronized void set(int period, Runnable callback) { + if (stopped()) return; + + long now = SystemClock.elapsedRealtime(); + MyEvent event = new MyEvent(period, callback, now); + insertEvent(event); + + if (mEventQueue.first() == event) { + if (mEventQueue.size() > 1) cancelAlarm(); + scheduleNext(); + } + + long triggerTime = event.mTriggerTime; + if (DEBUG_TIMER) { + Log.d(TAG, " add event " + event + " scheduled at " + + showTime(triggerTime) + " at " + showTime(now) + + ", #events=" + mEventQueue.size()); + printQueue(); + } + } + + /** + * Cancels all the timer events with the specified callback. + * + * @param callback the callback + */ + public synchronized void cancel(Runnable callback) { + if (stopped() || mEventQueue.isEmpty()) return; + if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); + + MyEvent firstEvent = mEventQueue.first(); + for (Iterator iter = mEventQueue.iterator(); + iter.hasNext();) { + MyEvent event = iter.next(); + if (event.mCallback == callback) { + iter.remove(); + if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); + } + } + if (mEventQueue.isEmpty()) { + cancelAlarm(); + } else if (mEventQueue.first() != firstEvent) { + cancelAlarm(); + firstEvent = mEventQueue.first(); + firstEvent.mPeriod = firstEvent.mMaxPeriod; + firstEvent.mTriggerTime = firstEvent.mLastTriggerTime + + firstEvent.mPeriod; + recalculatePeriods(); + scheduleNext(); + } + if (DEBUG_TIMER) { + Log.d(TAG, "after cancel:"); + printQueue(); + } + } + + private void scheduleNext() { + if (stopped() || mEventQueue.isEmpty()) return; + + if (mPendingIntent != null) { + throw new RuntimeException("pendingIntent is not null!"); + } + + MyEvent event = mEventQueue.first(); + Intent intent = new Intent(getAction()); + intent.putExtra(TRIGGER_TIME, event.mTriggerTime); + PendingIntent pendingIntent = mPendingIntent = + PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + event.mTriggerTime, pendingIntent); + } + + @Override + public synchronized void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (getAction().equals(action) + && intent.getExtras().containsKey(TRIGGER_TIME)) { + mPendingIntent = null; + long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); + execute(triggerTime); + } else { + Log.d(TAG, "unrecognized intent: " + intent); + } + } + + private void printQueue() { + int count = 0; + for (MyEvent event : mEventQueue) { + Log.d(TAG, " " + event + ": scheduled at " + + showTime(event.mTriggerTime) + ": last at " + + showTime(event.mLastTriggerTime)); + if (++count >= 5) break; + } + if (mEventQueue.size() > count) { + Log.d(TAG, " ....."); + } else if (count == 0) { + Log.d(TAG, " "); + } + } + + private void execute(long triggerTime) { + if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + + showTime(triggerTime) + ": " + mEventQueue.size()); + if (stopped() || mEventQueue.isEmpty()) return; + + for (MyEvent event : mEventQueue) { + if (event.mTriggerTime != triggerTime) break; + if (DEBUG_TIMER) Log.d(TAG, "execute " + event); + + event.mLastTriggerTime = event.mTriggerTime; + event.mTriggerTime += event.mPeriod; + + // run the callback in a new thread to prevent deadlock + new Thread(event.mCallback, "SipServiceTimerCallbackThread") + .start(); + } + if (DEBUG_TIMER) { + Log.d(TAG, "after timeout execution"); + printQueue(); + } + scheduleNext(); + } + + private String getAction() { + return toString(); + } + + private String showTime(long time) { + int ms = (int) (time % 1000); + int s = (int) (time / 1000); + int m = s / 60; + s %= 60; + return String.format("%d.%d.%d", m, s, ms); + } + } + + private static class MyEvent { + int mPeriod; + int mMaxPeriod; + long mTriggerTime; + long mLastTriggerTime; + Runnable mCallback; + + MyEvent(int period, Runnable callback, long now) { + mPeriod = mMaxPeriod = period; + mCallback = callback; + mLastTriggerTime = now; + } + + @Override + public String toString() { + String s = super.toString(); + s = s.substring(s.indexOf("@")); + return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" + + toString(mCallback); + } + + private String toString(Object o) { + String s = o.toString(); + int index = s.indexOf("$"); + if (index > 0) s = s.substring(index + 1); + return s; + } + } + + private static class MyEventComparator implements Comparator { + public int compare(MyEvent e1, MyEvent e2) { + if (e1 == e2) return 0; + int diff = e1.mMaxPeriod - e2.mMaxPeriod; + if (diff == 0) diff = -1; + return diff; + } + + public boolean equals(Object that) { + return (this == that); + } + } + + // Single-threaded executor + private static class MyExecutor extends Handler { + MyExecutor() { + super(createLooper()); + } + + private static Looper createLooper() { + HandlerThread thread = new HandlerThread("SipService"); + thread.start(); + return thread.getLooper(); + } + + void addTask(Runnable task) { + Message.obtain(this, 0/* don't care */, task).sendToTarget(); + } + + @Override + public void handleMessage(Message msg) { + if (msg.obj instanceof Runnable) { + ((Runnable) msg.obj).run(); + } else { + Log.w(TAG, "can't handle msg: " + msg); + } + } + } +} diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java new file mode 100644 index 0000000..91677a2 --- /dev/null +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -0,0 +1,1393 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import gov.nist.javax.sip.clientauthutils.AccountManager; +import gov.nist.javax.sip.clientauthutils.UserCredentials; +import gov.nist.javax.sip.header.SIPHeaderNames; +import gov.nist.javax.sip.header.ProxyAuthenticate; +import gov.nist.javax.sip.header.WWWAuthenticate; +import gov.nist.javax.sip.message.SIPMessage; + +import android.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SipErrorCode; +import android.net.sip.SipProfile; +import android.net.sip.SipSession; +import android.net.sip.SipSessionAdapter; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.DatagramSocket; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.util.Collection; +import java.util.EventObject; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.TooManyListenersException; + +import javax.sip.ClientTransaction; +import javax.sip.Dialog; +import javax.sip.DialogTerminatedEvent; +import javax.sip.IOExceptionEvent; +import javax.sip.InvalidArgumentException; +import javax.sip.ListeningPoint; +import javax.sip.RequestEvent; +import javax.sip.ResponseEvent; +import javax.sip.ServerTransaction; +import javax.sip.SipException; +import javax.sip.SipFactory; +import javax.sip.SipListener; +import javax.sip.SipProvider; +import javax.sip.SipStack; +import javax.sip.TimeoutEvent; +import javax.sip.Transaction; +import javax.sip.TransactionState; +import javax.sip.TransactionTerminatedEvent; +import javax.sip.TransactionUnavailableException; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.CSeqHeader; +import javax.sip.header.ExpiresHeader; +import javax.sip.header.FromHeader; +import javax.sip.header.MinExpiresHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Message; +import javax.sip.message.Request; +import javax.sip.message.Response; + +/** + * Manages {@link ISipSession}'s for a SIP account. + */ +class SipSessionGroup implements SipListener { + private static final String TAG = "SipSession"; + private static final boolean DEBUG = true; + private static final boolean DEBUG_PING = DEBUG && false; + private static final String ANONYMOUS = "anonymous"; + private static final String SERVER_ERROR_PREFIX = "Response: "; + private static final int EXPIRY_TIME = 3600; // in seconds + private static final int CANCEL_CALL_TIMER = 3; // in seconds + + private static final EventObject DEREGISTER = new EventObject("Deregister"); + private static final EventObject END_CALL = new EventObject("End call"); + private static final EventObject HOLD_CALL = new EventObject("Hold call"); + private static final EventObject CONTINUE_CALL + = new EventObject("Continue call"); + + private final SipProfile mLocalProfile; + private final String mPassword; + + private SipStack mSipStack; + private SipHelper mSipHelper; + private String mLastNonce; + private int mRPort; + + // session that processes INVITE requests + private SipSessionImpl mCallReceiverSession; + private String mLocalIp; + + // call-id-to-SipSession map + private Map mSessionMap = + new HashMap(); + + /** + * @param myself the local profile with password crossed out + * @param password the password of the profile + * @throws IOException if cannot assign requested address + */ + public SipSessionGroup(String localIp, SipProfile myself, String password) + throws SipException, IOException { + mLocalProfile = myself; + mPassword = password; + reset(localIp); + } + + synchronized void reset(String localIp) throws SipException, IOException { + mLocalIp = localIp; + if (localIp == null) return; + + SipProfile myself = mLocalProfile; + SipFactory sipFactory = SipFactory.getInstance(); + Properties properties = new Properties(); + properties.setProperty("javax.sip.STACK_NAME", getStackName()); + String outboundProxy = myself.getProxyAddress(); + if (!TextUtils.isEmpty(outboundProxy)) { + Log.v(TAG, "outboundProxy is " + outboundProxy); + properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy + + ":" + myself.getPort() + "/" + myself.getProtocol()); + } + SipStack stack = mSipStack = sipFactory.createSipStack(properties); + + try { + SipProvider provider = stack.createSipProvider( + stack.createListeningPoint(localIp, allocateLocalPort(), + myself.getProtocol())); + provider.addSipListener(this); + mSipHelper = new SipHelper(stack, provider); + } catch (InvalidArgumentException e) { + throw new IOException(e.getMessage()); + } catch (TooManyListenersException e) { + // must never happen + throw new SipException("SipSessionGroup constructor", e); + } + Log.d(TAG, " start stack for " + myself.getUriString()); + stack.start(); + + mLastNonce = null; + mCallReceiverSession = null; + mSessionMap.clear(); + } + + synchronized void onConnectivityChanged() { + for (SipSessionImpl s : mSessionMap.values()) { + s.onError(SipErrorCode.DATA_CONNECTION_LOST, + "data connection lost"); + } + } + + public SipProfile getLocalProfile() { + return mLocalProfile; + } + + public String getLocalProfileUri() { + return mLocalProfile.getUriString(); + } + + private String getStackName() { + return "stack" + System.currentTimeMillis(); + } + + public synchronized void close() { + Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); + mSessionMap.clear(); + closeToNotReceiveCalls(); + if (mSipStack != null) { + mSipStack.stop(); + mSipStack = null; + mSipHelper = null; + } + } + + public synchronized boolean isClosed() { + return (mSipStack == null); + } + + // For internal use, require listener not to block in callbacks. + public synchronized void openToReceiveCalls(ISipSessionListener listener) { + if (mCallReceiverSession == null) { + mCallReceiverSession = new SipSessionCallReceiverImpl(listener); + } else { + mCallReceiverSession.setListener(listener); + } + } + + public synchronized void closeToNotReceiveCalls() { + mCallReceiverSession = null; + } + + public ISipSession createSession(ISipSessionListener listener) { + return (isClosed() ? null : new SipSessionImpl(listener)); + } + + private static int allocateLocalPort() throws SipException { + try { + DatagramSocket s = new DatagramSocket(); + int localPort = s.getLocalPort(); + s.close(); + return localPort; + } catch (IOException e) { + throw new SipException("allocateLocalPort()", e); + } + } + + private synchronized SipSessionImpl getSipSession(EventObject event) { + String key = SipHelper.getCallId(event); + SipSessionImpl session = mSessionMap.get(key); + if ((session != null) && isLoggable(session)) { + Log.d(TAG, "session key from event: " + key); + Log.d(TAG, "active sessions:"); + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); + } + } + return ((session != null) ? session : mCallReceiverSession); + } + + private synchronized void addSipSession(SipSessionImpl newSession) { + removeSipSession(newSession); + String key = newSession.getCallId(); + mSessionMap.put(key, newSession); + if (isLoggable(newSession)) { + Log.d(TAG, "+++ add a session with key: '" + key + "'"); + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); + } + } + } + + private synchronized void removeSipSession(SipSessionImpl session) { + if (session == mCallReceiverSession) return; + String key = session.getCallId(); + SipSessionImpl s = mSessionMap.remove(key); + // sanity check + if ((s != null) && (s != session)) { + Log.w(TAG, "session " + session + " is not associated with key '" + + key + "'"); + mSessionMap.put(key, s); + for (Map.Entry entry + : mSessionMap.entrySet()) { + if (entry.getValue() == s) { + key = entry.getKey(); + mSessionMap.remove(key); + } + } + } + + if ((s != null) && isLoggable(s)) { + Log.d(TAG, "remove session " + session + " @key '" + key + "'"); + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); + } + } + } + + public void processRequest(RequestEvent event) { + process(event); + } + + public void processResponse(ResponseEvent event) { + process(event); + } + + public void processIOException(IOExceptionEvent event) { + process(event); + } + + public void processTimeout(TimeoutEvent event) { + process(event); + } + + public void processTransactionTerminated(TransactionTerminatedEvent event) { + process(event); + } + + public void processDialogTerminated(DialogTerminatedEvent event) { + process(event); + } + + private synchronized void process(EventObject event) { + SipSessionImpl session = getSipSession(event); + try { + boolean isLoggable = isLoggable(session, event); + boolean processed = (session != null) && session.process(event); + if (isLoggable && processed) { + Log.d(TAG, "new state after: " + + SipSession.State.toString(session.mState)); + } + } catch (Throwable e) { + Log.w(TAG, "event process error: " + event, e); + session.onError(e); + } + } + + private String extractContent(Message message) { + // Currently we do not support secure MIME bodies. + byte[] bytes = message.getRawContent(); + if (bytes != null) { + try { + if (message instanceof SIPMessage) { + return ((SIPMessage) message).getMessageContent(); + } else { + return new String(bytes, "UTF-8"); + } + } catch (UnsupportedEncodingException e) { + } + } + return null; + } + + private class SipSessionCallReceiverImpl extends SipSessionImpl { + public SipSessionCallReceiverImpl(ISipSessionListener listener) { + super(listener); + } + + public boolean process(EventObject evt) throws SipException { + if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + + SipSession.State.toString(mState) + ": processing " + + log(evt)); + if (isRequestEvent(Request.INVITE, evt)) { + RequestEvent event = (RequestEvent) evt; + SipSessionImpl newSession = new SipSessionImpl(mProxy); + newSession.mServerTransaction = mSipHelper.sendRinging(event, + generateTag()); + newSession.mDialog = newSession.mServerTransaction.getDialog(); + newSession.mInviteReceived = event; + newSession.mPeerProfile = createPeerProfile(event.getRequest()); + newSession.mState = SipSession.State.INCOMING_CALL; + newSession.mPeerSessionDescription = + extractContent(event.getRequest()); + addSipSession(newSession); + mProxy.onRinging(newSession, newSession.mPeerProfile, + newSession.mPeerSessionDescription); + return true; + } else if (isRequestEvent(Request.OPTIONS, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + return true; + } else { + return false; + } + } + } + + class SipSessionImpl extends ISipSession.Stub { + SipProfile mPeerProfile; + SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); + int mState = SipSession.State.READY_TO_CALL; + RequestEvent mInviteReceived; + Dialog mDialog; + ServerTransaction mServerTransaction; + ClientTransaction mClientTransaction; + String mPeerSessionDescription; + boolean mInCall; + boolean mReRegisterFlag = false; + SessionTimer mTimer; + + // lightweight timer + class SessionTimer { + private boolean mRunning = true; + + void start(final int timeout) { + new Thread(new Runnable() { + public void run() { + sleep(timeout); + if (mRunning) timeout(); + } + }, "SipSessionTimerThread").start(); + } + + synchronized void cancel() { + mRunning = false; + this.notify(); + } + + private void timeout() { + synchronized (SipSessionGroup.this) { + onError(SipErrorCode.TIME_OUT, "Session timed out!"); + } + } + + private synchronized void sleep(int timeout) { + try { + this.wait(timeout * 1000); + } catch (InterruptedException e) { + Log.e(TAG, "session timer interrupted!"); + } + } + } + + public SipSessionImpl(ISipSessionListener listener) { + setListener(listener); + } + + SipSessionImpl duplicate() { + return new SipSessionImpl(mProxy.getListener()); + } + + private void reset() { + mInCall = false; + removeSipSession(this); + mPeerProfile = null; + mState = SipSession.State.READY_TO_CALL; + mInviteReceived = null; + mDialog = null; + mServerTransaction = null; + mClientTransaction = null; + mPeerSessionDescription = null; + + cancelSessionTimer(); + } + + public boolean isInCall() { + return mInCall; + } + + public String getLocalIp() { + return mLocalIp; + } + + public SipProfile getLocalProfile() { + return mLocalProfile; + } + + public SipProfile getPeerProfile() { + return mPeerProfile; + } + + public String getCallId() { + return SipHelper.getCallId(getTransaction()); + } + + private Transaction getTransaction() { + if (mClientTransaction != null) return mClientTransaction; + if (mServerTransaction != null) return mServerTransaction; + return null; + } + + public int getState() { + return mState; + } + + public void setListener(ISipSessionListener listener) { + mProxy.setListener((listener instanceof SipSessionListenerProxy) + ? ((SipSessionListenerProxy) listener).getListener() + : listener); + } + + // process the command in a new thread + private void doCommandAsync(final EventObject command) { + new Thread(new Runnable() { + public void run() { + try { + processCommand(command); + } catch (SipException e) { + Log.w(TAG, "command error: " + command, e); + onError(e); + } + } + }, "SipSessionAsyncCmdThread").start(); + } + + public void makeCall(SipProfile peerProfile, String sessionDescription, + int timeout) { + doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, + timeout)); + } + + public void answerCall(String sessionDescription, int timeout) { + try { + processCommand(new MakeCallCommand(mPeerProfile, + sessionDescription, timeout)); + } catch (SipException e) { + onError(e); + } + } + + public void endCall() { + doCommandAsync(END_CALL); + } + + public void changeCall(String sessionDescription, int timeout) { + doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, + timeout)); + } + + public void changeCallWithTimeout( + String sessionDescription, int timeout) { + doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, + timeout)); + } + + public void register(int duration) { + doCommandAsync(new RegisterCommand(duration)); + } + + public void unregister() { + doCommandAsync(DEREGISTER); + } + + public boolean isReRegisterRequired() { + return mReRegisterFlag; + } + + public void clearReRegisterRequired() { + mReRegisterFlag = false; + } + + public void sendKeepAlive() { + mState = SipSession.State.PINGING; + try { + processCommand(new OptionsCommand()); + while (SipSession.State.PINGING == mState) { + Thread.sleep(1000); + } + } catch (SipException e) { + Log.e(TAG, "sendKeepAlive failed", e); + } catch (InterruptedException e) { + Log.e(TAG, "sendKeepAlive interrupted", e); + } + } + + private void processCommand(EventObject command) throws SipException { + if (!process(command)) { + onError(SipErrorCode.IN_PROGRESS, + "cannot initiate a new transaction to execute: " + + command); + } + } + + protected String generateTag() { + // 32-bit randomness + return String.valueOf((long) (Math.random() * 0x100000000L)); + } + + public String toString() { + try { + String s = super.toString(); + return s.substring(s.indexOf("@")) + ":" + + SipSession.State.toString(mState); + } catch (Throwable e) { + return super.toString(); + } + } + + public boolean process(EventObject evt) throws SipException { + if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + + SipSession.State.toString(mState) + ": processing " + + log(evt)); + synchronized (SipSessionGroup.this) { + if (isClosed()) return false; + + Dialog dialog = null; + if (evt instanceof RequestEvent) { + dialog = ((RequestEvent) evt).getDialog(); + } else if (evt instanceof ResponseEvent) { + dialog = ((ResponseEvent) evt).getDialog(); + } + if (dialog != null) mDialog = dialog; + + boolean processed; + + switch (mState) { + case SipSession.State.REGISTERING: + case SipSession.State.DEREGISTERING: + processed = registeringToReady(evt); + break; + case SipSession.State.PINGING: + processed = keepAliveProcess(evt); + break; + case SipSession.State.READY_TO_CALL: + processed = readyForCall(evt); + break; + case SipSession.State.INCOMING_CALL: + processed = incomingCall(evt); + break; + case SipSession.State.INCOMING_CALL_ANSWERING: + processed = incomingCallToInCall(evt); + break; + case SipSession.State.OUTGOING_CALL: + case SipSession.State.OUTGOING_CALL_RING_BACK: + processed = outgoingCall(evt); + break; + case SipSession.State.OUTGOING_CALL_CANCELING: + processed = outgoingCallToReady(evt); + break; + case SipSession.State.IN_CALL: + processed = inCall(evt); + break; + default: + processed = false; + } + return (processed || processExceptions(evt)); + } + } + + private boolean processExceptions(EventObject evt) throws SipException { + if (isRequestEvent(Request.BYE, evt)) { + // terminate the call whenever a BYE is received + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, + Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); + return true; + } else if (evt instanceof TransactionTerminatedEvent) { + if (isCurrentTransaction((TransactionTerminatedEvent) evt)) { + if (evt instanceof TimeoutEvent) { + processTimeout((TimeoutEvent) evt); + } else { + processTransactionTerminated( + (TransactionTerminatedEvent) evt); + } + return true; + } + } else if (isRequestEvent(Request.OPTIONS, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + return true; + } else if (evt instanceof DialogTerminatedEvent) { + processDialogTerminated((DialogTerminatedEvent) evt); + return true; + } + return false; + } + + private void processDialogTerminated(DialogTerminatedEvent event) { + if (mDialog == event.getDialog()) { + onError(new SipException("dialog terminated")); + } else { + Log.d(TAG, "not the current dialog; current=" + mDialog + + ", terminated=" + event.getDialog()); + } + } + + private boolean isCurrentTransaction(TransactionTerminatedEvent event) { + Transaction current = event.isServerTransaction() + ? mServerTransaction + : mClientTransaction; + Transaction target = event.isServerTransaction() + ? event.getServerTransaction() + : event.getClientTransaction(); + + if ((current != target) && (mState != SipSession.State.PINGING)) { + Log.d(TAG, "not the current transaction; current=" + + toString(current) + ", target=" + toString(target)); + return false; + } else if (current != null) { + Log.d(TAG, "transaction terminated: " + toString(current)); + return true; + } else { + // no transaction; shouldn't be here; ignored + return true; + } + } + + private String toString(Transaction transaction) { + if (transaction == null) return "null"; + Request request = transaction.getRequest(); + Dialog dialog = transaction.getDialog(); + CSeqHeader cseq = (CSeqHeader) request.getHeader(CSeqHeader.NAME); + return String.format("req=%s,%s,s=%s,ds=%s,", request.getMethod(), + cseq.getSeqNumber(), transaction.getState(), + ((dialog == null) ? "-" : dialog.getState())); + } + + private void processTransactionTerminated( + TransactionTerminatedEvent event) { + switch (mState) { + case SipSession.State.IN_CALL: + case SipSession.State.READY_TO_CALL: + Log.d(TAG, "Transaction terminated; do nothing"); + break; + default: + Log.d(TAG, "Transaction terminated early: " + this); + onError(SipErrorCode.TRANSACTION_TERMINTED, + "transaction terminated"); + } + } + + private void processTimeout(TimeoutEvent event) { + Log.d(TAG, "processing Timeout..."); + switch (mState) { + case SipSession.State.REGISTERING: + case SipSession.State.DEREGISTERING: + reset(); + mProxy.onRegistrationTimeout(this); + break; + 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 SipSession.State.PINGING: + reset(); + mReRegisterFlag = true; + mState = SipSession.State.READY_TO_CALL; + break; + + default: + Log.d(TAG, " do nothing"); + break; + } + } + + private int getExpiryTime(Response response) { + int expires = EXPIRY_TIME; + ExpiresHeader expiresHeader = (ExpiresHeader) + response.getHeader(ExpiresHeader.NAME); + if (expiresHeader != null) expires = expiresHeader.getExpires(); + expiresHeader = (ExpiresHeader) + response.getHeader(MinExpiresHeader.NAME); + if (expiresHeader != null) { + expires = Math.max(expires, expiresHeader.getExpires()); + } + return expires; + } + + private boolean keepAliveProcess(EventObject evt) throws SipException { + if (evt instanceof OptionsCommand) { + mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, + generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + return true; + } else if (evt instanceof ResponseEvent) { + return parseOptionsResult(evt); + } + return false; + } + + private boolean parseOptionsResult(EventObject evt) { + if (expectResponse(Request.OPTIONS, evt)) { + ResponseEvent event = (ResponseEvent) evt; + int rPort = getRPortFromResponse(event.getResponse()); + if (rPort != -1) { + if (mRPort == 0) mRPort = rPort; + if (mRPort != rPort) { + mReRegisterFlag = true; + if (DEBUG) Log.w(TAG, String.format( + "rport is changed: %d <> %d", mRPort, rPort)); + mRPort = rPort; + } else { + if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); + } + } else { + if (DEBUG) Log.w(TAG, "peer did not respond rport"); + } + reset(); + return true; + } + return false; + } + + private int getRPortFromResponse(Response response) { + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + return (viaHeader == null) ? -1 : viaHeader.getRPort(); + } + + private boolean registeringToReady(EventObject evt) + throws SipException { + if (expectResponse(Request.REGISTER, evt)) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + + int statusCode = response.getStatusCode(); + switch (statusCode) { + case Response.OK: + int state = mState; + onRegistrationDone((state == SipSession.State.REGISTERING) + ? getExpiryTime(((ResponseEvent) evt).getResponse()) + : -1); + mLastNonce = null; + mRPort = 0; + return true; + case Response.UNAUTHORIZED: + case Response.PROXY_AUTHENTICATION_REQUIRED: + if (!handleAuthentication(event)) { + if (mLastNonce == null) { + onRegistrationFailed(SipErrorCode.SERVER_ERROR, + "server does not provide challenge"); + } else { + Log.v(TAG, "Incorrect username/password"); + onRegistrationFailed( + SipErrorCode.INVALID_CREDENTIALS, + "incorrect username or password"); + } + } + return true; + default: + if (statusCode >= 500) { + onRegistrationFailed(response); + return true; + } + } + } + return false; + } + + private boolean handleAuthentication(ResponseEvent event) + throws SipException { + Response response = event.getResponse(); + String nonce = getNonceFromResponse(response); + if (((nonce != null) && nonce.equals(mLastNonce)) || + (nonce == null)) { + mLastNonce = nonce; + return false; + } else { + mClientTransaction = mSipHelper.handleChallenge( + event, getAccountManager()); + mDialog = mClientTransaction.getDialog(); + mLastNonce = nonce; + return true; + } + } + + private boolean crossDomainAuthenticationRequired(Response response) { + String realm = getRealmFromResponse(response); + if (realm == null) realm = ""; + return !mLocalProfile.getSipDomain().trim().equals(realm.trim()); + } + + private AccountManager getAccountManager() { + return new AccountManager() { + public UserCredentials getCredentials(ClientTransaction + challengedTransaction, String realm) { + return new UserCredentials() { + public String getUserName() { + return mLocalProfile.getUserName(); + } + + public String getPassword() { + return mPassword; + } + + public String getSipDomain() { + return mLocalProfile.getSipDomain(); + } + }; + } + }; + } + + private String getRealmFromResponse(Response response) { + WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( + SIPHeaderNames.WWW_AUTHENTICATE); + if (wwwAuth != null) return wwwAuth.getRealm(); + ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( + SIPHeaderNames.PROXY_AUTHENTICATE); + return (proxyAuth == null) ? null : proxyAuth.getRealm(); + } + + private String getNonceFromResponse(Response response) { + WWWAuthenticate wwwAuth = (WWWAuthenticate)response.getHeader( + SIPHeaderNames.WWW_AUTHENTICATE); + if (wwwAuth != null) return wwwAuth.getNonce(); + ProxyAuthenticate proxyAuth = (ProxyAuthenticate)response.getHeader( + SIPHeaderNames.PROXY_AUTHENTICATE); + return (proxyAuth == null) ? null : proxyAuth.getNonce(); + } + + private boolean readyForCall(EventObject evt) throws SipException { + // expect MakeCallCommand, RegisterCommand, DEREGISTER + if (evt instanceof MakeCallCommand) { + MakeCallCommand cmd = (MakeCallCommand) evt; + mPeerProfile = cmd.getPeerProfile(); + mClientTransaction = mSipHelper.sendInvite(mLocalProfile, + mPeerProfile, cmd.getSessionDescription(), + generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSession.State.OUTGOING_CALL; + mProxy.onCalling(this); + startSessionTimer(cmd.getTimeout()); + return true; + } else if (evt instanceof RegisterCommand) { + int duration = ((RegisterCommand) evt).getDuration(); + mClientTransaction = mSipHelper.sendRegister(mLocalProfile, + generateTag(), duration); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSession.State.REGISTERING; + mProxy.onRegistering(this); + return true; + } else if (DEREGISTER == evt) { + mClientTransaction = mSipHelper.sendRegister(mLocalProfile, + generateTag(), 0); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSession.State.DEREGISTERING; + mProxy.onRegistering(this); + return true; + } + return false; + } + + private boolean incomingCall(EventObject evt) throws SipException { + // expect MakeCallCommand(answering) , END_CALL cmd , Cancel + if (evt instanceof MakeCallCommand) { + // answer call + mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, + mLocalProfile, + ((MakeCallCommand) evt).getSessionDescription(), + mServerTransaction); + mState = SipSession.State.INCOMING_CALL_ANSWERING; + startSessionTimer(((MakeCallCommand) evt).getTimeout()); + return true; + } else if (END_CALL == evt) { + mSipHelper.sendInviteBusyHere(mInviteReceived, + mServerTransaction); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + RequestEvent event = (RequestEvent) evt; + mSipHelper.sendResponse(event, Response.OK); + mSipHelper.sendInviteRequestTerminated( + mInviteReceived.getRequest(), mServerTransaction); + endCallNormally(); + return true; + } + return false; + } + + private boolean incomingCallToInCall(EventObject evt) + throws SipException { + // expect ACK, CANCEL request + if (isRequestEvent(Request.ACK, evt)) { + establishCall(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + // http://tools.ietf.org/html/rfc3261#section-9.2 + // Final response has been sent; do nothing here. + return true; + } + return false; + } + + private boolean outgoingCall(EventObject evt) throws SipException { + if (expectResponse(Request.INVITE, evt)) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + + int statusCode = response.getStatusCode(); + switch (statusCode) { + case Response.RINGING: + if (mState == SipSession.State.OUTGOING_CALL) { + mState = SipSession.State.OUTGOING_CALL_RING_BACK; + mProxy.onRingingBack(this); + cancelSessionTimer(); + } + return true; + case Response.OK: + mSipHelper.sendInviteAck(event, mDialog); + mPeerSessionDescription = extractContent(response); + establishCall(); + return true; + case Response.UNAUTHORIZED: + case Response.PROXY_AUTHENTICATION_REQUIRED: + if (crossDomainAuthenticationRequired(response)) { + onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, + getRealmFromResponse(response)); + } else if (handleAuthentication(event)) { + addSipSession(this); + } else if (mLastNonce == null) { + onError(SipErrorCode.SERVER_ERROR, + "server does not provide challenge"); + } else { + onError(SipErrorCode.INVALID_CREDENTIALS, + "incorrect username or password"); + } + return true; + case Response.REQUEST_PENDING: + // TODO: + // rfc3261#section-14.1; re-schedule invite + return true; + default: + if (statusCode >= 400) { + // error: an ack is sent automatically by the stack + onError(response); + return true; + } else if (statusCode >= 300) { + // TODO: handle 3xx (redirect) + } else { + return true; + } + } + return false; + } else if (END_CALL == evt) { + // RFC says that UA should not send out cancel when no + // response comes back yet. We are cheating for not checking + // response. + mSipHelper.sendCancel(mClientTransaction); + mState = SipSession.State.OUTGOING_CALL_CANCELING; + startSessionTimer(CANCEL_CALL_TIMER); + return true; + } + return false; + } + + private boolean outgoingCallToReady(EventObject evt) + throws SipException { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + int statusCode = response.getStatusCode(); + if (expectResponse(Request.CANCEL, evt)) { + if (statusCode == Response.OK) { + // do nothing; wait for REQUEST_TERMINATED + return true; + } + } else if (expectResponse(Request.INVITE, evt)) { + switch (statusCode) { + case Response.OK: + outgoingCall(evt); // abort Cancel + return true; + case Response.REQUEST_TERMINATED: + endCallNormally(); + return true; + } + } else { + return false; + } + + if (statusCode >= 400) { + onError(response); + return true; + } + } else if (evt instanceof TransactionTerminatedEvent) { + // rfc3261#section-14.1: + // if re-invite gets timed out, terminate the dialog; but + // re-invite is not reliable, just let it go and pretend + // nothing happened. + onError(new SipException("timed out")); + } + return false; + } + + private boolean inCall(EventObject evt) throws SipException { + // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) + // OK retransmission is handled in SipStack + if (END_CALL == evt) { + // rfc3261#section-15.1.1 + mSipHelper.sendBye(mDialog); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.INVITE, evt)) { + // got Re-INVITE + RequestEvent event = mInviteReceived = (RequestEvent) evt; + mState = SipSession.State.INCOMING_CALL; + mPeerSessionDescription = extractContent(event.getRequest()); + mServerTransaction = null; + mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); + return true; + } else if (isRequestEvent(Request.BYE, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + endCallNormally(); + return true; + } else if (evt instanceof MakeCallCommand) { + // to change call + mClientTransaction = mSipHelper.sendReinvite(mDialog, + ((MakeCallCommand) evt).getSessionDescription()); + mState = SipSession.State.OUTGOING_CALL; + startSessionTimer(((MakeCallCommand) evt).getTimeout()); + return true; + } + return false; + } + + // timeout in seconds + private void startSessionTimer(int timeout) { + if (timeout > 0) { + mTimer = new SessionTimer(); + mTimer.start(timeout); + } + } + + private void cancelSessionTimer() { + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + } + + private String createErrorMessage(Response response) { + return String.format(SERVER_ERROR_PREFIX + "%s (%d)", + response.getReasonPhrase(), response.getStatusCode()); + } + + private void establishCall() { + mState = SipSession.State.IN_CALL; + mInCall = true; + cancelSessionTimer(); + mProxy.onCallEstablished(this, mPeerSessionDescription); + } + + private void fallbackToPreviousInCall(int errorCode, String message) { + mState = SipSession.State.IN_CALL; + mProxy.onCallChangeFailed(this, errorCode, message); + } + + private void endCallNormally() { + reset(); + mProxy.onCallEnded(this); + } + + private void endCallOnError(int errorCode, String message) { + reset(); + mProxy.onError(this, errorCode, message); + } + + private void endCallOnBusy() { + reset(); + mProxy.onCallBusy(this); + } + + private void onError(int errorCode, String message) { + cancelSessionTimer(); + switch (mState) { + case SipSession.State.REGISTERING: + case SipSession.State.DEREGISTERING: + onRegistrationFailed(errorCode, message); + break; + default: + if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST) + && mInCall) { + fallbackToPreviousInCall(errorCode, message); + } else { + endCallOnError(errorCode, message); + } + } + } + + + private void onError(Throwable exception) { + exception = getRootCause(exception); + onError(getErrorCode(exception), exception.toString()); + } + + private void onError(Response response) { + int statusCode = response.getStatusCode(); + if (!mInCall && (statusCode == Response.BUSY_HERE)) { + endCallOnBusy(); + } else { + onError(getErrorCode(statusCode), createErrorMessage(response)); + } + } + + private int getErrorCode(int responseStatusCode) { + switch (responseStatusCode) { + case Response.TEMPORARILY_UNAVAILABLE: + case Response.FORBIDDEN: + case Response.GONE: + case Response.NOT_FOUND: + case Response.NOT_ACCEPTABLE: + case Response.NOT_ACCEPTABLE_HERE: + return SipErrorCode.PEER_NOT_REACHABLE; + + case Response.REQUEST_URI_TOO_LONG: + case Response.ADDRESS_INCOMPLETE: + case Response.AMBIGUOUS: + return SipErrorCode.INVALID_REMOTE_URI; + + case Response.REQUEST_TIMEOUT: + return SipErrorCode.TIME_OUT; + + default: + if (responseStatusCode < 500) { + return SipErrorCode.CLIENT_ERROR; + } else { + return SipErrorCode.SERVER_ERROR; + } + } + } + + private Throwable getRootCause(Throwable exception) { + Throwable cause = exception.getCause(); + while (cause != null) { + exception = cause; + cause = exception.getCause(); + } + return exception; + } + + private int getErrorCode(Throwable exception) { + String message = exception.getMessage(); + if (exception instanceof UnknownHostException) { + return SipErrorCode.INVALID_REMOTE_URI; + } else if (exception instanceof IOException) { + return SipErrorCode.SOCKET_ERROR; + } else if (message.startsWith(SERVER_ERROR_PREFIX)) { + return SipErrorCode.SERVER_ERROR; + } else { + return SipErrorCode.CLIENT_ERROR; + } + } + + private void onRegistrationDone(int duration) { + reset(); + mProxy.onRegistrationDone(this, duration); + } + + private void onRegistrationFailed(int errorCode, String message) { + reset(); + mProxy.onRegistrationFailed(this, errorCode, message); + } + + private void onRegistrationFailed(Throwable exception) { + reset(); + exception = getRootCause(exception); + onRegistrationFailed(getErrorCode(exception), + exception.toString()); + } + + private void onRegistrationFailed(Response response) { + reset(); + int statusCode = response.getStatusCode(); + onRegistrationFailed(getErrorCode(statusCode), + createErrorMessage(response)); + } + } + + /** + * @return true if the event is a request event matching the specified + * method; false otherwise + */ + private static boolean isRequestEvent(String method, EventObject event) { + try { + if (event instanceof RequestEvent) { + RequestEvent requestEvent = (RequestEvent) event; + return method.equals(requestEvent.getRequest().getMethod()); + } + } catch (Throwable e) { + } + return false; + } + + private static String getCseqMethod(Message message) { + return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); + } + + /** + * @return true if the event is a response event and the CSeqHeader method + * match the given arguments; false otherwise + */ + private static boolean expectResponse( + String expectedMethod, EventObject evt) { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); + } + return false; + } + + /** + * @return true if the event is a response event and the response code and + * CSeqHeader method match the given arguments; false otherwise + */ + private static boolean expectResponse( + int responseCode, String expectedMethod, EventObject evt) { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + if (response.getStatusCode() == responseCode) { + return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); + } + } + return false; + } + + private static SipProfile createPeerProfile(Request request) + throws SipException { + try { + FromHeader fromHeader = + (FromHeader) request.getHeader(FromHeader.NAME); + Address address = fromHeader.getAddress(); + SipURI uri = (SipURI) address.getURI(); + String username = uri.getUser(); + if (username == null) username = ANONYMOUS; + return new SipProfile.Builder(username, uri.getHost()) + .setPort(uri.getPort()) + .setDisplayName(address.getDisplayName()) + .build(); + } catch (IllegalArgumentException e) { + throw new SipException("createPeerProfile()", e); + } catch (ParseException e) { + throw new SipException("createPeerProfile()", e); + } + } + + private static boolean isLoggable(SipSessionImpl s) { + if (s != null) { + switch (s.mState) { + case SipSession.State.PINGING: + return DEBUG_PING; + } + } + return DEBUG; + } + + private static boolean isLoggable(SipSessionImpl s, EventObject evt) { + if (!isLoggable(s)) return false; + if (evt == null) return false; + + if (evt instanceof OptionsCommand) { + return DEBUG_PING; + } else if (evt instanceof ResponseEvent) { + Response response = ((ResponseEvent) evt).getResponse(); + if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { + return DEBUG_PING; + } + return DEBUG; + } else if (evt instanceof RequestEvent) { + return DEBUG; + } + return false; + } + + private static String log(EventObject evt) { + if (evt instanceof RequestEvent) { + return ((RequestEvent) evt).getRequest().toString(); + } else if (evt instanceof ResponseEvent) { + return ((ResponseEvent) evt).getResponse().toString(); + } else { + return evt.toString(); + } + } + + private class OptionsCommand extends EventObject { + public OptionsCommand() { + super(SipSessionGroup.this); + } + } + + private class RegisterCommand extends EventObject { + private int mDuration; + + public RegisterCommand(int duration) { + super(SipSessionGroup.this); + mDuration = duration; + } + + public int getDuration() { + return mDuration; + } + } + + private class MakeCallCommand extends EventObject { + private String mSessionDescription; + private int mTimeout; // in seconds + + public MakeCallCommand(SipProfile peerProfile, + String sessionDescription) { + this(peerProfile, sessionDescription, -1); + } + + public MakeCallCommand(SipProfile peerProfile, + String sessionDescription, int timeout) { + super(peerProfile); + mSessionDescription = sessionDescription; + mTimeout = timeout; + } + + public SipProfile getPeerProfile() { + return (SipProfile) getSource(); + } + + public String getSessionDescription() { + return mSessionDescription; + } + + public int getTimeout() { + return mTimeout; + } + } +} diff --git a/java/com/android/server/sip/SipSessionListenerProxy.java b/java/com/android/server/sip/SipSessionListenerProxy.java new file mode 100644 index 0000000..f8be0a8 --- /dev/null +++ b/java/com/android/server/sip/SipSessionListenerProxy.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import android.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SipProfile; +import android.os.DeadObjectException; +import android.util.Log; + +/** Class to help safely run a callback in a different thread. */ +class SipSessionListenerProxy extends ISipSessionListener.Stub { + private static final String TAG = "SipSession"; + + private ISipSessionListener mListener; + + public void setListener(ISipSessionListener listener) { + mListener = listener; + } + + public ISipSessionListener getListener() { + return mListener; + } + + private void proxy(Runnable runnable) { + // 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, "SipSessionCallbackThread").start(); + } + + public void onCalling(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCalling(session); + } catch (Throwable t) { + handle(t, "onCalling()"); + } + } + }); + } + + public void onRinging(final ISipSession session, final SipProfile caller, + final String sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRinging(session, caller, sessionDescription); + } catch (Throwable t) { + handle(t, "onRinging()"); + } + } + }); + } + + public void onRingingBack(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRingingBack(session); + } catch (Throwable t) { + handle(t, "onRingingBack()"); + } + } + }); + } + + public void onCallEstablished(final ISipSession session, + final String sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallEstablished(session, sessionDescription); + } catch (Throwable t) { + handle(t, "onCallEstablished()"); + } + } + }); + } + + public void onCallEnded(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallEnded(session); + } catch (Throwable t) { + handle(t, "onCallEnded()"); + } + } + }); + } + + public void onCallBusy(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallBusy(session); + } catch (Throwable t) { + handle(t, "onCallBusy()"); + } + } + }); + } + + public void onCallChangeFailed(final ISipSession session, + final int errorCode, final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallChangeFailed(session, errorCode, message); + } catch (Throwable t) { + handle(t, "onCallChangeFailed()"); + } + } + }); + } + + public void onError(final ISipSession session, final int errorCode, + final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onError(session, errorCode, message); + } catch (Throwable t) { + handle(t, "onError()"); + } + } + }); + } + + public void onRegistering(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistering(session); + } catch (Throwable t) { + handle(t, "onRegistering()"); + } + } + }); + } + + public void onRegistrationDone(final ISipSession session, + final int duration) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationDone(session, duration); + } catch (Throwable t) { + handle(t, "onRegistrationDone()"); + } + } + }); + } + + public void onRegistrationFailed(final ISipSession session, + final int errorCode, final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationFailed(session, errorCode, message); + } catch (Throwable t) { + handle(t, "onRegistrationFailed()"); + } + } + }); + } + + public void onRegistrationTimeout(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationTimeout(session); + } catch (Throwable t) { + handle(t, "onRegistrationTimeout()"); + } + } + }); + } + + private void handle(Throwable t, String message) { + if (t instanceof DeadObjectException) { + mListener = null; + // This creates race but it's harmless. Just don't log the error + // when it happens. + } else if (mListener != null) { + Log.w(TAG, message, t); + } + } +} -- cgit v1.2.3 From 3b9456929376a047ef38b5ddb27fed9b11664306 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 28 Sep 2010 07:53:39 +0800 Subject: SIP: add DisconnectCause.SERVER_ERROR and fix how SipErrorCode.SERVER_ERROR is determinted from server response, not from local exceptions. http://b/issue?id=3041332 Change-Id: Idce67e29858d5c7573b98b7fa1fac074913d71d6 --- java/com/android/server/sip/SipSessionGroup.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 91677a2..8f9a26b 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -82,7 +82,6 @@ class SipSessionGroup implements SipListener { private static final boolean DEBUG = true; private static final boolean DEBUG_PING = DEBUG && false; private static final String ANONYMOUS = "anonymous"; - private static final String SERVER_ERROR_PREFIX = "Response: "; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds @@ -1099,8 +1098,8 @@ class SipSessionGroup implements SipListener { } private String createErrorMessage(Response response) { - return String.format(SERVER_ERROR_PREFIX + "%s (%d)", - response.getReasonPhrase(), response.getStatusCode()); + return String.format("%s (%d)", response.getReasonPhrase(), + response.getStatusCode()); } private void establishCall() { @@ -1204,8 +1203,6 @@ class SipSessionGroup implements SipListener { return SipErrorCode.INVALID_REMOTE_URI; } else if (exception instanceof IOException) { return SipErrorCode.SOCKET_ERROR; - } else if (message.startsWith(SERVER_ERROR_PREFIX)) { - return SipErrorCode.SERVER_ERROR; } else { return SipErrorCode.CLIENT_ERROR; } -- cgit v1.2.3 From 6ca8ec4793540c6c0ad83574aa52e316a8ed17b4 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 29 Sep 2010 01:52:16 +0800 Subject: SIP: Feedback any provisional responses in addition to RING The only exception is TRYING. Also remove an unused import in SipSessionGroup. http://b/issue?id=3021865 Change-Id: I160982b0c4b417362f1fb961217db90c3a585ce5 --- java/com/android/server/sip/SipSessionGroup.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 8f9a26b..4321d7b 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -28,7 +28,6 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipErrorCode; import android.net.sip.SipProfile; import android.net.sip.SipSession; -import android.net.sip.SipSessionAdapter; import android.text.TextUtils; import android.util.Log; @@ -959,6 +958,11 @@ class SipSessionGroup implements SipListener { int statusCode = response.getStatusCode(); switch (statusCode) { case Response.RINGING: + case Response.CALL_IS_BEING_FORWARDED: + case Response.QUEUED: + case Response.SESSION_PROGRESS: + // feedback any provisional responses (except TRYING) as + // ring back for better UX if (mState == SipSession.State.OUTGOING_CALL) { mState = SipSession.State.OUTGOING_CALL_RING_BACK; mProxy.onRingingBack(this); -- cgit v1.2.3 From d3cd43211e94d0cd1dbb7d0c35b01412eea4b5b8 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 29 Sep 2010 05:19:44 +0800 Subject: RTP: Delay the initialization of AudioTrack and AudioRecord. Related to http://b/3043844. Change-Id: I2c4fd9f64e6eba597d68b2ea1ceeff83103697db --- jni/rtp/AudioGroup.cpp | 256 ++++++++++++++++++++++++------------------------- 1 file changed, 123 insertions(+), 133 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 81d4dfc..72c882b 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -461,18 +461,15 @@ private: EC_ENABLED = 3, LAST_MODE = 3, }; - int mMode; + AudioStream *mChain; int mEventQueue; volatile int mDtmfEvent; + int mMode; + int mSampleRate; int mSampleCount; int mDeviceSocket; - AudioTrack mTrack; - AudioRecord mRecord; - - bool networkLoop(); - bool deviceLoop(); class NetworkThread : public Thread { @@ -490,10 +487,7 @@ private: private: AudioGroup *mGroup; - bool threadLoop() - { - return mGroup->networkLoop(); - } + bool threadLoop(); }; sp mNetworkThread; @@ -504,9 +498,6 @@ private: bool start() { - char c; - while (recv(mGroup->mDeviceSocket, &c, 1, MSG_DONTWAIT) == 1); - if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { LOGE("cannot start device thread"); return false; @@ -516,10 +507,7 @@ private: private: AudioGroup *mGroup; - bool threadLoop() - { - return mGroup->deviceLoop(); - } + bool threadLoop(); }; sp mDeviceThread; }; @@ -539,8 +527,6 @@ AudioGroup::~AudioGroup() { mNetworkThread->requestExitAndWait(); mDeviceThread->requestExitAndWait(); - mTrack.stop(); - mRecord.stop(); close(mEventQueue); close(mDeviceSocket); while (mChain) { @@ -559,40 +545,9 @@ bool AudioGroup::set(int sampleRate, int sampleCount) return false; } + mSampleRate = sampleRate; mSampleCount = sampleCount; - // Find out the frame count for AudioTrack and AudioRecord. - int output = 0; - int input = 0; - if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, - sampleRate) != NO_ERROR || output <= 0 || - AudioRecord::getMinFrameCount(&input, sampleRate, - AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { - LOGE("cannot compute frame count"); - return false; - } - LOGD("reported frame count: output %d, input %d", output, input); - - if (output < sampleCount * 2) { - output = sampleCount * 2; - } - if (input < sampleCount * 2) { - input = sampleCount * 2; - } - LOGD("adjusted frame count: output %d, input %d", output, input); - - // Initialize AudioTrack and AudioRecord. - if (mTrack.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || - mRecord.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { - LOGE("cannot initialize audio device"); - return false; - } - LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); - - // TODO: initialize echo canceler here. - // Create device socket. int pair[2]; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { @@ -610,13 +565,11 @@ bool AudioGroup::set(int sampleRate, int sampleCount) return false; } - // Give device socket a reasonable timeout and buffer size. + // Give device socket a reasonable timeout. timeval tv; tv.tv_sec = 0; tv.tv_usec = 1000 * sampleCount / sampleRate * 500; - if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || - setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || - setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { + if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { LOGE("setsockopt: %s", strerror(errno)); return false; } @@ -644,29 +597,10 @@ bool AudioGroup::setMode(int mode) return true; } + mDeviceThread->requestExitAndWait(); LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); mMode = mode; - - mDeviceThread->requestExitAndWait(); - if (mode == ON_HOLD) { - mTrack.stop(); - mRecord.stop(); - return true; - } - - mTrack.start(); - if (mode == MUTED) { - mRecord.stop(); - } else { - mRecord.start(); - } - - if (!mDeviceThread->start()) { - mTrack.stop(); - mRecord.stop(); - return false; - } - return true; + return (mode == ON_HOLD) || mDeviceThread->start(); } bool AudioGroup::sendDtmf(int event) @@ -741,15 +675,16 @@ bool AudioGroup::remove(int socket) return true; } -bool AudioGroup::networkLoop() +bool AudioGroup::NetworkThread::threadLoop() { + AudioStream *chain = mGroup->mChain; int tick = elapsedRealtime(); int deadline = tick + 10; int count = 0; - for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + for (AudioStream *stream = chain; stream; stream = stream->mNext) { if (!stream->mTick || tick - stream->mTick >= 0) { - stream->encode(tick, mChain); + stream->encode(tick, chain); } if (deadline - stream->mTick > 0) { deadline = stream->mTick; @@ -757,12 +692,12 @@ bool AudioGroup::networkLoop() ++count; } - if (mDtmfEvent != -1) { - int event = mDtmfEvent; - for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + int event = mGroup->mDtmfEvent; + if (event != -1) { + for (AudioStream *stream = chain; stream; stream = stream->mNext) { stream->sendDtmf(event); } - mDtmfEvent = -1; + mGroup->mDtmfEvent = -1; } deadline -= tick; @@ -771,7 +706,7 @@ bool AudioGroup::networkLoop() } epoll_event events[count]; - count = epoll_wait(mEventQueue, events, count, deadline); + count = epoll_wait(mGroup->mEventQueue, events, count, deadline); if (count == -1) { LOGE("epoll_wait: %s", strerror(errno)); return false; @@ -783,70 +718,125 @@ bool AudioGroup::networkLoop() return true; } -bool AudioGroup::deviceLoop() +bool AudioGroup::DeviceThread::threadLoop() { - int16_t output[mSampleCount]; + int mode = mGroup->mMode; + int sampleRate = mGroup->mSampleRate; + int sampleCount = mGroup->mSampleCount; + int deviceSocket = mGroup->mDeviceSocket; - if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) { - memset(output, 0, sizeof(output)); + // Find out the frame count for AudioTrack and AudioRecord. + int output = 0; + int input = 0; + if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord::getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; } + LOGD("reported frame count: output %d, input %d", output, input); - int16_t input[mSampleCount]; - int toWrite = mSampleCount; - int toRead = (mMode == MUTED) ? 0 : mSampleCount; - int chances = 100; + if (output < sampleCount * 2) { + output = sampleCount * 2; + } + if (input < sampleCount * 2) { + input = sampleCount * 2; + } + LOGD("adjusted frame count: output %d, input %d", output, input); - while (--chances > 0 && (toWrite > 0 || toRead > 0)) { - if (toWrite > 0) { - AudioTrack::Buffer buffer; - buffer.frameCount = toWrite; + // Initialize AudioTrack and AudioRecord. + AudioTrack track; + AudioRecord record; + if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || + record.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { + LOGE("cannot initialize audio device"); + return false; + } + LOGD("latency: output %d, input %d", track.latency(), record.latency()); - status_t status = mTrack.obtainBuffer(&buffer, 1); - if (status == NO_ERROR) { - memcpy(buffer.i8, &output[mSampleCount - toWrite], buffer.size); - toWrite -= buffer.frameCount; - mTrack.releaseBuffer(&buffer); - } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot write to AudioTrack"); - return false; - } + // TODO: initialize echo canceler here. + + // Give device socket a reasonable buffer size. + setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); + setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output)); + + // Drain device socket. + char c; + while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); + + // Start your engine! + track.start(); + if (mode != MUTED) { + record.start(); + } + + while (!exitPending()) { + int16_t output[sampleCount]; + if (recv(deviceSocket, output, sizeof(output), 0) <= 0) { + memset(output, 0, sizeof(output)); } - if (toRead > 0) { - AudioRecord::Buffer buffer; - buffer.frameCount = mRecord.frameCount(); - - status_t status = mRecord.obtainBuffer(&buffer, 1); - if (status == NO_ERROR) { - int count = ((int)buffer.frameCount < toRead) ? - buffer.frameCount : toRead; - memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2); - toRead -= count; - if (buffer.frameCount < mRecord.frameCount()) { - buffer.frameCount = count; + int16_t input[sampleCount]; + int toWrite = sampleCount; + int toRead = (mode == MUTED) ? 0 : sampleCount; + int chances = 100; + + while (--chances > 0 && (toWrite > 0 || toRead > 0)) { + if (toWrite > 0) { + AudioTrack::Buffer buffer; + buffer.frameCount = toWrite; + + status_t status = track.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + int offset = sampleCount - toWrite; + memcpy(buffer.i8, &output[offset], buffer.size); + toWrite -= buffer.frameCount; + track.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot write to AudioTrack"); + break; + } + } + + if (toRead > 0) { + AudioRecord::Buffer buffer; + buffer.frameCount = record.frameCount(); + + status_t status = record.obtainBuffer(&buffer, 1); + if (status == NO_ERROR) { + int count = ((int)buffer.frameCount < toRead) ? + buffer.frameCount : toRead; + memcpy(&input[sampleCount - toRead], buffer.i8, count * 2); + toRead -= count; + if (buffer.frameCount < record.frameCount()) { + buffer.frameCount = count; + } + record.releaseBuffer(&buffer); + } else if (status != TIMED_OUT && status != WOULD_BLOCK) { + LOGE("cannot read from AudioRecord"); + break; } - mRecord.releaseBuffer(&buffer); - } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot read from AudioRecord"); - return false; } } - } - if (!chances) { - LOGE("device loop timeout"); - return false; - } + if (chances <= 0) { + LOGE("device loop timeout"); + break; + } - if (mMode != MUTED) { - if (mMode == NORMAL) { - send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); - } else { - // TODO: Echo canceller runs here. - send(mDeviceSocket, input, sizeof(input), MSG_DONTWAIT); + if (mode != MUTED) { + if (mode == NORMAL) { + send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); + } else { + // TODO: Echo canceller runs here. + send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); + } } } - return true; + return false; } //------------------------------------------------------------------------------ -- cgit v1.2.3 From 536a451f5cf9f4bcf38968366cf62368f3d4d2f8 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 29 Sep 2010 05:46:19 +0800 Subject: RTP: Refactor out G711 codecs into another file. Change-Id: I38dbefef2315a28d44683e86a51e69f38e3f20ec --- jni/rtp/Android.mk | 1 + jni/rtp/AudioCodec.cpp | 122 +------------------------------------------ jni/rtp/G711Codec.cpp | 138 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 120 deletions(-) create mode 100644 jni/rtp/G711Codec.cpp diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index a364355..69afd49 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -22,6 +22,7 @@ LOCAL_MODULE := librtp_jni LOCAL_SRC_FILES := \ AudioCodec.cpp \ AudioGroup.cpp \ + G711Codec.cpp \ RtpStream.cpp \ util.cpp \ rtp_jni.cpp diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index 4d8d36c..8912d82 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -18,124 +18,8 @@ #include "AudioCodec.h" -namespace { - -int8_t gExponents[128] = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, -}; - -//------------------------------------------------------------------------------ - -class UlawCodec : public AudioCodec -{ -public: - int set(int sampleRate, const char *fmtp) { - mSampleCount = sampleRate / 50; - return mSampleCount; - } - int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); -private: - int mSampleCount; -}; - -int UlawCodec::encode(void *payload, int16_t *samples) -{ - int8_t *ulaws = (int8_t *)payload; - for (int i = 0; i < mSampleCount; ++i) { - int sample = samples[i]; - int sign = (sample >> 8) & 0x80; - if (sample < 0) { - sample = -sample; - } - sample += 132; - if (sample > 32767) { - sample = 32767; - } - int exponent = gExponents[sample >> 8]; - int mantissa = (sample >> (exponent + 3)) & 0x0F; - ulaws[i] = ~(sign | (exponent << 4) | mantissa); - } - return mSampleCount; -} - -int UlawCodec::decode(int16_t *samples, void *payload, int length) -{ - int8_t *ulaws = (int8_t *)payload; - for (int i = 0; i < length; ++i) { - int ulaw = ~ulaws[i]; - int exponent = (ulaw >> 4) & 0x07; - int mantissa = ulaw & 0x0F; - int sample = (((mantissa << 3) + 132) << exponent) - 132; - samples[i] = (ulaw < 0 ? -sample : sample); - } - return length; -} - -AudioCodec *newUlawCodec() -{ - return new UlawCodec; -} - -//------------------------------------------------------------------------------ - -class AlawCodec : public AudioCodec -{ -public: - int set(int sampleRate, const char *fmtp) { - mSampleCount = sampleRate / 50; - return mSampleCount; - } - int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); -private: - int mSampleCount; -}; - -int AlawCodec::encode(void *payload, int16_t *samples) -{ - int8_t *alaws = (int8_t *)payload; - for (int i = 0; i < mSampleCount; ++i) { - int sample = samples[i]; - int sign = (sample >> 8) & 0x80; - if (sample < 0) { - sample = -sample; - } - if (sample > 32767) { - sample = 32767; - } - int exponent = gExponents[sample >> 8]; - int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; - alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; - } - return mSampleCount; -} - -int AlawCodec::decode(int16_t *samples, void *payload, int length) -{ - int8_t *alaws = (int8_t *)payload; - for (int i = 0; i < length; ++i) { - int alaw = alaws[i] ^ 0x55; - int exponent = (alaw >> 4) & 0x07; - int mantissa = alaw & 0x0F; - int sample = (exponent == 0 ? (mantissa << 4) + 8 : - ((mantissa << 3) + 132) << exponent); - samples[i] = (alaw < 0 ? sample : -sample); - } - return length; -} - -AudioCodec *newAlawCodec() -{ - return new AlawCodec; -} +extern AudioCodec *newAlawCodec(); +extern AudioCodec *newUlawCodec(); struct AudioCodecType { const char *name; @@ -146,8 +30,6 @@ struct AudioCodecType { {NULL, NULL}, }; -} // namespace - AudioCodec *newAudioCodec(const char *codecName) { AudioCodecType *type = gAudioCodecTypes; diff --git a/jni/rtp/G711Codec.cpp b/jni/rtp/G711Codec.cpp new file mode 100644 index 0000000..091afa9 --- /dev/null +++ b/jni/rtp/G711Codec.cpp @@ -0,0 +1,138 @@ +/* + * Copyrightm (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. + */ + +#include "AudioCodec.h" + +namespace { + +int8_t gExponents[128] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +//------------------------------------------------------------------------------ + +class UlawCodec : public AudioCodec +{ +public: + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int UlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + sample += 132; + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent + 3)) & 0x0F; + ulaws[i] = ~(sign | (exponent << 4) | mantissa); + } + return mSampleCount; +} + +int UlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int ulaw = ~ulaws[i]; + int exponent = (ulaw >> 4) & 0x07; + int mantissa = ulaw & 0x0F; + int sample = (((mantissa << 3) + 132) << exponent) - 132; + samples[i] = (ulaw < 0 ? -sample : sample); + } + return length; +} + +//------------------------------------------------------------------------------ + +class AlawCodec : public AudioCodec +{ +public: + int set(int sampleRate, const char *fmtp) { + mSampleCount = sampleRate / 50; + return mSampleCount; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int AlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; + alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; + } + return mSampleCount; +} + +int AlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int alaw = alaws[i] ^ 0x55; + int exponent = (alaw >> 4) & 0x07; + int mantissa = alaw & 0x0F; + int sample = (exponent == 0 ? (mantissa << 4) + 8 : + ((mantissa << 3) + 132) << exponent); + samples[i] = (alaw < 0 ? sample : -sample); + } + return length; +} + +} // namespace + +AudioCodec *newUlawCodec() +{ + return new UlawCodec; +} + +AudioCodec *newAlawCodec() +{ + return new AlawCodec; +} -- cgit v1.2.3 From d0f061251d77b98b510626ddcc925a74195519df Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 29 Sep 2010 10:36:52 +0800 Subject: RTP: Enable GSM codec. Change-Id: Iae1913fb0643f1c66b5d16f24d51924d363e5ef5 --- java/android/net/rtp/AudioCodec.java | 2 +- jni/rtp/Android.mk | 10 +++-- jni/rtp/AudioCodec.cpp | 2 + jni/rtp/GsmCodec.cpp | 74 ++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 jni/rtp/GsmCodec.cpp diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java index 4851a46..dfa6841 100644 --- a/java/android/net/rtp/AudioCodec.java +++ b/java/android/net/rtp/AudioCodec.java @@ -81,7 +81,7 @@ public class AudioCodec { public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); // TODO: add rest of the codecs when the native part is done. - private static final AudioCodec[] sCodecs = {PCMU, PCMA}; + private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA}; private AudioCodec(int type, String rtpmap, String fmtp) { this.type = type; diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 69afd49..29683bd 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -22,21 +22,25 @@ LOCAL_MODULE := librtp_jni LOCAL_SRC_FILES := \ AudioCodec.cpp \ AudioGroup.cpp \ - G711Codec.cpp \ RtpStream.cpp \ util.cpp \ rtp_jni.cpp +LOCAL_SRC_FILES += \ + G711Codec.cpp \ + GsmCodec.cpp + LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ libmedia -LOCAL_STATIC_LIBRARIES := +LOCAL_STATIC_LIBRARIES := libgsm LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) + $(JNI_H_INCLUDE) \ + external/libgsm/inc LOCAL_CFLAGS += -fvisibility=hidden diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index 8912d82..fc33ef2 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -20,6 +20,7 @@ extern AudioCodec *newAlawCodec(); extern AudioCodec *newUlawCodec(); +extern AudioCodec *newGsmCodec(); struct AudioCodecType { const char *name; @@ -27,6 +28,7 @@ struct AudioCodecType { } gAudioCodecTypes[] = { {"PCMA", newAlawCodec}, {"PCMU", newUlawCodec}, + {"GSM", newGsmCodec}, {NULL, NULL}, }; diff --git a/jni/rtp/GsmCodec.cpp b/jni/rtp/GsmCodec.cpp new file mode 100644 index 0000000..8d2286e --- /dev/null +++ b/jni/rtp/GsmCodec.cpp @@ -0,0 +1,74 @@ +/* + * Copyrightm (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. + */ + +#include "AudioCodec.h" + +extern "C" { +#include "gsm.h" +} + +namespace { + +class GsmCodec : public AudioCodec +{ +public: + GsmCodec() { + mEncode = gsm_create(); + mDecode = gsm_create(); + } + + ~GsmCodec() { + if (mEncode) { + gsm_destroy(mEncode); + } + if (mDecode) { + gsm_destroy(mDecode); + } + } + + int set(int sampleRate, const char *fmtp) { + return (sampleRate == 8000 && mEncode && mDecode) ? 160 : -1; + } + + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + gsm mEncode; + gsm mDecode; +}; + +int GsmCodec::encode(void *payload, int16_t *samples) +{ + gsm_encode(mEncode, samples, (unsigned char *)payload); + return 33; +} + +int GsmCodec::decode(int16_t *samples, void *payload, int length) +{ + if (length == 33 && + gsm_decode(mDecode, (unsigned char *)payload, samples) == 0) { + return 160; + } + return -1; +} + +} // namespace + +AudioCodec *newGsmCodec() +{ + return new GsmCodec; +} -- cgit v1.2.3 From f703412e867486aef60daf3f025d03794c19821f Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 30 Sep 2010 02:42:27 +0800 Subject: RTP: Revise the workaround of private addresses and fix bugs. Change-Id: Ie654b569f47049aa452eca8d3e6d4a98ac18469c --- jni/rtp/AudioGroup.cpp | 51 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 72c882b..f09edb6 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -86,8 +86,6 @@ public: void decode(int tick); private: - bool isNatAddress(struct sockaddr_storage *addr); - enum { NORMAL = 0, SEND_ONLY = 1, @@ -101,6 +99,7 @@ private: AudioCodec *mCodec; uint32_t mCodecMagic; uint32_t mDtmfMagic; + bool mFixRemote; int mTick; int mSampleRate; @@ -181,6 +180,20 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, if (codec) { mRemote = *remote; mCodec = codec; + + // Here we should never get an private address, but some buggy proxy + // servers do give us one. To solve this, we replace the address when + // the first time we successfully decode an incoming packet. + mFixRemote = false; + if (remote->ss_family == AF_INET) { + unsigned char *address = + (unsigned char *)&((sockaddr_in *)remote)->sin_addr; + if (address[0] == 10 || + (address[0] == 172 && (address[1] >> 4) == 1) || + (address[0] == 192 && address[1] == 168)) { + mFixRemote = true; + } + } } LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, @@ -318,16 +331,6 @@ void AudioStream::encode(int tick, AudioStream *chain) sizeof(mRemote)); } -bool AudioStream::isNatAddress(struct sockaddr_storage *addr) { - if (addr->ss_family != AF_INET) return false; - struct sockaddr_in *s4 = (struct sockaddr_in *)addr; - unsigned char *d = (unsigned char *) &s4->sin_addr; - if ((d[0] == 10) - || ((d[0] == 172) && (d[1] & 0x10)) - || ((d[0] == 192) && (d[1] == 168))) return true; - return false; -} - void AudioStream::decode(int tick) { char c; @@ -375,21 +378,11 @@ void AudioStream::decode(int tick) MSG_TRUNC | MSG_DONTWAIT) >> 1; } else { __attribute__((aligned(4))) uint8_t buffer[2048]; - struct sockaddr_storage src_addr; - socklen_t addrlen; + sockaddr_storage remote; + socklen_t len = sizeof(remote); + length = recvfrom(mSocket, buffer, sizeof(buffer), - MSG_TRUNC|MSG_DONTWAIT, (sockaddr*)&src_addr, &addrlen); - - // The following if clause is for fixing the target address if - // proxy server did not replace the NAT address with its media - // port in SDP. Although it is proxy server's responsibility for - // replacing the connection address with correct one, we will change - // the target address as we detect the difference for now until we - // know the best way to get rid of this issue. - if ((memcmp((void*)&src_addr, (void*)&mRemote, addrlen) != 0) && - isNatAddress(&mRemote)) { - memcpy((void*)&mRemote, (void*)&src_addr, addrlen); - } + MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &len); // Do we need to check SSRC, sequence, and timestamp? They are not // reliable but at least they can be used to identify duplicates? @@ -409,8 +402,12 @@ void AudioStream::decode(int tick) if (length >= 0) { length = mCodec->decode(samples, &buffer[offset], length); } + if (length > 0 && mFixRemote) { + mRemote = remote; + mFixRemote = false; + } } - if (length != mSampleCount) { + if (length <= 0) { LOGD("stream[%d] decoder error", mSocket); return; } -- cgit v1.2.3 From ac8b0e5a92e1a9abdaa543656fba0cf52ca8d786 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 30 Sep 2010 03:04:06 +0800 Subject: RTP: Enable GSM-EFR codec. Change-Id: I9d84009e4557a0a82c1f9d7d543922741be97c77 --- java/android/net/rtp/AudioCodec.java | 2 +- jni/rtp/AmrCodec.cpp | 97 ++++++++++++++++++++++++++++++++++++ jni/rtp/Android.mk | 12 ++++- jni/rtp/AudioCodec.cpp | 2 + 4 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 jni/rtp/AmrCodec.cpp diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java index dfa6841..f171806 100644 --- a/java/android/net/rtp/AudioCodec.java +++ b/java/android/net/rtp/AudioCodec.java @@ -81,7 +81,7 @@ public class AudioCodec { public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); // TODO: add rest of the codecs when the native part is done. - private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA}; + private static final AudioCodec[] sCodecs = {GSM_EFR, GSM, PCMU, PCMA}; private AudioCodec(int type, String rtpmap, String fmtp) { this.type = type; diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp new file mode 100644 index 0000000..9a2227d --- /dev/null +++ b/jni/rtp/AmrCodec.cpp @@ -0,0 +1,97 @@ +/* + * Copyrightm (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. + */ + +#include "AudioCodec.h" + +#include "gsmamr_dec.h" +#include "gsmamr_enc.h" + +namespace { + +class GsmEfrCodec : public AudioCodec +{ +public: + GsmEfrCodec() { + if (AMREncodeInit(&mEncoder, &mSidSync, false)) { + mEncoder = NULL; + } + if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) { + mDecoder = NULL; + } + } + + ~GsmEfrCodec() { + if (mEncoder) { + AMREncodeExit(&mEncoder, &mSidSync); + } + if (mDecoder) { + GSMDecodeFrameExit(&mDecoder); + } + } + + int set(int sampleRate, const char *fmtp) { + return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1; + } + + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + void *mEncoder; + void *mSidSync; + void *mDecoder; +}; + +int GsmEfrCodec::encode(void *payload, int16_t *samples) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + + int length = AMREncode(mEncoder, mSidSync, MR122, + samples, bytes, &type, AMR_TX_WMF); + + if (type == AMR_122 && length == 32) { + bytes[0] = 0xC0 | (bytes[1] >> 4); + for (int i = 1; i < 31; ++i) { + bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4); + } + return 31; + } + return -1; +} + +int GsmEfrCodec::decode(int16_t *samples, void *payload, int length) +{ + unsigned char *bytes = (unsigned char *)payload; + if (length == 31 && (bytes[0] >> 4) == 0x0C) { + for (int i = 0; i < 30; ++i) { + bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4); + } + bytes[30] <<= 4; + + if (AMRDecode(mDecoder, AMR_122, bytes, samples, MIME_IETF) == 31) { + return 160; + } + } + return -1; +} + +} // namespace + +AudioCodec *newGsmEfrCodec() +{ + return new GsmEfrCodec; +} diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 29683bd..5909c0d 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -27,6 +27,7 @@ LOCAL_SRC_FILES := \ rtp_jni.cpp LOCAL_SRC_FILES += \ + AmrCodec.cpp \ G711Codec.cpp \ GsmCodec.cpp @@ -34,13 +35,20 @@ LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ - libmedia + libmedia \ + libstagefright LOCAL_STATIC_LIBRARIES := libgsm LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ - external/libgsm/inc + external/libgsm/inc \ + frameworks/base/media/libstagefright/codecs/amrnb/common/include \ + frameworks/base/media/libstagefright/codecs/amrnb/common/ \ + frameworks/base/media/libstagefright/codecs/amrnb/enc/include \ + frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ + frameworks/base/media/libstagefright/codecs/amrnb/dec/include \ + frameworks/base/media/libstagefright/codecs/amrnb/dec/src LOCAL_CFLAGS += -fvisibility=hidden diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index fc33ef2..afc193c 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -21,6 +21,7 @@ extern AudioCodec *newAlawCodec(); extern AudioCodec *newUlawCodec(); extern AudioCodec *newGsmCodec(); +extern AudioCodec *newGsmEfrCodec(); struct AudioCodecType { const char *name; @@ -29,6 +30,7 @@ struct AudioCodecType { {"PCMA", newAlawCodec}, {"PCMU", newUlawCodec}, {"GSM", newGsmCodec}, + {"GSM-EFR", newGsmEfrCodec}, {NULL, NULL}, }; -- cgit v1.2.3 From d8d3b15f408314ac88201eee3e401a35556ba669 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 30 Sep 2010 07:49:35 +0800 Subject: SIP: misc fixes. + Fix keepalive timer event leak due to the race between stopping timer and the async'ed timeout handler + SipSessionImpl: set state before handling an event to ensure we get correct state when some error occurs during handling the event. + Fix potential NPE in SipManager.ListenerRelay.getUri(). Change-Id: I021ee34f83059fd4fbb64b30bea427a5462aa51b --- java/android/net/sip/SipManager.java | 4 +- java/com/android/server/sip/SipService.java | 131 ++++++++++++++--------- java/com/android/server/sip/SipSessionGroup.java | 17 ++- 3 files changed, 94 insertions(+), 58 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 59631c1..52f5716 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -520,7 +520,9 @@ public class SipManager { private String getUri(ISipSession session) { try { - return session.getLocalProfile().getUriString(); + return ((session == null) + ? "no session" + : session.getLocalProfile().getUriString()); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 0ff5586..130fe9f 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -510,31 +510,43 @@ public final class SipService extends ISipService.Stub { } } + // KeepAliveProcess is controlled by AutoRegistrationProcess. + // All methods will be invoked in sync with SipService.this except realRun() private class KeepAliveProcess implements Runnable { private static final String TAG = "\\KEEPALIVE/"; private static final int INTERVAL = 10; private SipSessionGroup.SipSessionImpl mSession; + private boolean mRunning = false; public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { mSession = session; } public void start() { + if (mRunning) return; + mRunning = true; mTimer.set(INTERVAL * 1000, this); } + // timeout handler public void run() { + if (!mRunning) return; + final SipSessionGroup.SipSessionImpl session = mSession; + // delegate to mExecutor getExecutor().addTask(new Runnable() { public void run() { - realRun(); + realRun(session); } }); } - private void realRun() { + // real timeout handler + private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + if (notCurrentSession(session)) return; + + session = session.duplicate(); if (DEBUG) Log.d(TAG, "~~~ keepalive"); mTimer.cancel(this); session.sendKeepAlive(); @@ -547,8 +559,14 @@ public final class SipService extends ISipService.Stub { } public void stop() { + mRunning = false; + mSession = null; mTimer.cancel(this); } + + private boolean notCurrentSession(ISipSession session) { + return (session != mSession) || !mRunning; + } } private class AutoRegistrationProcess extends SipSessionAdapter @@ -561,13 +579,15 @@ public final class SipService extends ISipService.Stub { private long mExpiryTime; private int mErrorCode; private String mErrorMessage; + private boolean mRunning = false; private String getAction() { return toString(); } public void start(SipSessionGroup group) { - if (mSession == null) { + if (!mRunning) { + mRunning = true; mBackoff = 1; mSession = (SipSessionGroup.SipSessionImpl) group.createSession(this); @@ -584,35 +604,24 @@ public final class SipService extends ISipService.Stub { } public void stop() { - stop(false); - } - - private void stopButKeepStates() { - stop(true); - } - - private void stop(boolean keepStates) { - if (mSession == null) return; + if (!mRunning) return; + mRunning = false; + mSession.setListener(null); if (mConnected && mRegistered) mSession.unregister(); + mTimer.cancel(this); if (mKeepAliveProcess != null) { mKeepAliveProcess.stop(); mKeepAliveProcess = null; } - if (!keepStates) { - mSession = null; - mRegistered = false; - } - } - private boolean isStopped() { - return (mSession == null); + mRegistered = false; + setListener(mProxy.getListener()); } public void setListener(ISipSessionListener listener) { synchronized (SipService.this) { mProxy.setListener(listener); - if (mSession == null) return; try { int state = (mSession == null) @@ -632,6 +641,18 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationFailed(mSession, mErrorCode, mErrorMessage); } + } else if (!mConnected) { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.DATA_CONNECTION_LOST, + "no data connection"); + } else if (!mRunning) { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.CLIENT_ERROR, + "registration not running"); + } else { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.IN_PROGRESS, + String.valueOf(state)); } } catch (Throwable t) { Log.w(TAG, "setListener(): " + t); @@ -643,21 +664,29 @@ public final class SipService extends ISipService.Stub { return mRegistered; } + // timeout handler public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); + synchronized (SipService.this) { + if (!mRunning) return; + final SipSessionGroup.SipSessionImpl session = mSession; + + // delegate to mExecutor + getExecutor().addTask(new Runnable() { + public void run() { + realRun(session); + } + }); + } } - private void realRun() { - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); + // real timeout handler + private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); + if (notCurrentSession(session)) return; + mErrorCode = SipErrorCode.NO_ERROR; + mErrorMessage = null; + if (DEBUG) Log.d(TAG, "~~~ registering"); + if (mConnected) session.register(EXPIRY_TIME); } } @@ -697,22 +726,29 @@ public final class SipService extends ISipService.Stub { public void onRegistering(ISipSession session) { if (DEBUG) Log.d(TAG, "onRegistering(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; + mRegistered = false; mProxy.onRegistering(session); } } + private boolean notCurrentSession(ISipSession session) { + if (session != mSession) { + ((SipSessionGroup.SipSessionImpl) session).setListener(null); + return true; + } + return !mRunning; + } + @Override public void onRegistrationDone(ISipSession session, int duration) { if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; mProxy.onRegistrationDone(session, duration); - if (isStopped()) return; - if (duration > 0) { mSession.clearReRegisterRequired(); mExpiryTime = SystemClock.elapsedRealtime() @@ -751,17 +787,18 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " + SipErrorCode.toString(errorCode) + ": " + message); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = errorCode; - mErrorMessage = message; - mProxy.onRegistrationFailed(session, errorCode, message); + if (notCurrentSession(session)) return; if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { if (DEBUG) Log.d(TAG, " pause auto-registration"); - stopButKeepStates(); - } else if (!isStopped()) { + stop(); + } else { onError(); } + + mErrorCode = errorCode; + mErrorMessage = message; + mProxy.onRegistrationFailed(session, errorCode, message); } } @@ -769,14 +806,11 @@ public final class SipService extends ISipService.Stub { public void onRegistrationTimeout(ISipSession session) { if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; + mErrorCode = SipErrorCode.TIME_OUT; mProxy.onRegistrationTimeout(session); - - if (!isStopped()) { - mRegistered = false; - onError(); - } + onError(); } } @@ -883,6 +917,7 @@ public final class SipService extends ISipService.Stub { mConnected = connected; } + // timeout handler @Override public void run() { // delegate to mExecutor diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 4321d7b..c68fa1b 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -334,12 +334,12 @@ class SipSessionGroup implements SipListener { if (isRequestEvent(Request.INVITE, evt)) { RequestEvent event = (RequestEvent) evt; SipSessionImpl newSession = new SipSessionImpl(mProxy); + newSession.mState = SipSession.State.INCOMING_CALL; newSession.mServerTransaction = mSipHelper.sendRinging(event, generateTag()); newSession.mDialog = newSession.mServerTransaction.getDialog(); newSession.mInviteReceived = event; newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mState = SipSession.State.INCOMING_CALL; newSession.mPeerSessionDescription = extractContent(event.getRequest()); addSipSession(newSession); @@ -708,7 +708,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.PINGING: reset(); mReRegisterFlag = true; - mState = SipSession.State.READY_TO_CALL; break; default: @@ -877,6 +876,7 @@ class SipSessionGroup implements SipListener { private boolean readyForCall(EventObject evt) throws SipException { // expect MakeCallCommand, RegisterCommand, DEREGISTER if (evt instanceof MakeCallCommand) { + mState = SipSession.State.OUTGOING_CALL; MakeCallCommand cmd = (MakeCallCommand) evt; mPeerProfile = cmd.getPeerProfile(); mClientTransaction = mSipHelper.sendInvite(mLocalProfile, @@ -884,25 +884,24 @@ class SipSessionGroup implements SipListener { generateTag()); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.OUTGOING_CALL; mProxy.onCalling(this); startSessionTimer(cmd.getTimeout()); return true; } else if (evt instanceof RegisterCommand) { + mState = SipSession.State.REGISTERING; int duration = ((RegisterCommand) evt).getDuration(); mClientTransaction = mSipHelper.sendRegister(mLocalProfile, generateTag(), duration); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.REGISTERING; mProxy.onRegistering(this); return true; } else if (DEREGISTER == evt) { + mState = SipSession.State.DEREGISTERING; mClientTransaction = mSipHelper.sendRegister(mLocalProfile, generateTag(), 0); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.DEREGISTERING; mProxy.onRegistering(this); return true; } @@ -913,11 +912,11 @@ class SipSessionGroup implements SipListener { // expect MakeCallCommand(answering) , END_CALL cmd , Cancel if (evt instanceof MakeCallCommand) { // answer call + mState = SipSession.State.INCOMING_CALL_ANSWERING; mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile, ((MakeCallCommand) evt).getSessionDescription(), mServerTransaction); - mState = SipSession.State.INCOMING_CALL_ANSWERING; startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } else if (END_CALL == evt) { @@ -1009,8 +1008,8 @@ class SipSessionGroup implements SipListener { // RFC says that UA should not send out cancel when no // response comes back yet. We are cheating for not checking // response. - mSipHelper.sendCancel(mClientTransaction); mState = SipSession.State.OUTGOING_CALL_CANCELING; + mSipHelper.sendCancel(mClientTransaction); startSessionTimer(CANCEL_CALL_TIMER); return true; } @@ -1065,8 +1064,8 @@ class SipSessionGroup implements SipListener { return true; } else if (isRequestEvent(Request.INVITE, evt)) { // got Re-INVITE - RequestEvent event = mInviteReceived = (RequestEvent) evt; mState = SipSession.State.INCOMING_CALL; + RequestEvent event = mInviteReceived = (RequestEvent) evt; mPeerSessionDescription = extractContent(event.getRequest()); mServerTransaction = null; mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); @@ -1077,9 +1076,9 @@ class SipSessionGroup implements SipListener { return true; } else if (evt instanceof MakeCallCommand) { // to change call + mState = SipSession.State.OUTGOING_CALL; mClientTransaction = mSipHelper.sendReinvite(mDialog, ((MakeCallCommand) evt).getSessionDescription()); - mState = SipSession.State.OUTGOING_CALL; startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } -- cgit v1.2.3 From 2437b7d03ce3c0c63ffe04e15a53174d15ee8479 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 30 Sep 2010 08:51:59 +0800 Subject: RTP: Enable AMR codec. Change-Id: I49e6bdc1b67306b44173f2f346f8372a50264870 --- java/android/net/rtp/AudioCodec.java | 3 +- jni/rtp/AmrCodec.cpp | 171 +++++++++++++++++++++++++++++++++++ jni/rtp/AudioCodec.cpp | 2 + 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java index f171806..3877aeb 100644 --- a/java/android/net/rtp/AudioCodec.java +++ b/java/android/net/rtp/AudioCodec.java @@ -80,8 +80,7 @@ public class AudioCodec { */ public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); - // TODO: add rest of the codecs when the native part is done. - private static final AudioCodec[] sCodecs = {GSM_EFR, GSM, PCMU, PCMA}; + private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA}; private AudioCodec(int type, String rtpmap, String fmtp) { this.type = type; diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp index 9a2227d..f3ecac2 100644 --- a/jni/rtp/AmrCodec.cpp +++ b/jni/rtp/AmrCodec.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "AudioCodec.h" #include "gsmamr_dec.h" @@ -21,6 +23,170 @@ namespace { +const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244}; + +//------------------------------------------------------------------------------ + +// See RFC 4867 for the encoding details. + +class AmrCodec : public AudioCodec +{ +public: + AmrCodec() { + if (AMREncodeInit(&mEncoder, &mSidSync, false)) { + mEncoder = NULL; + } + if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) { + mDecoder = NULL; + } + } + + ~AmrCodec() { + if (mEncoder) { + AMREncodeExit(&mEncoder, &mSidSync); + } + if (mDecoder) { + GSMDecodeFrameExit(&mDecoder); + } + } + + int set(int sampleRate, const char *fmtp); + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + void *mEncoder; + void *mSidSync; + void *mDecoder; + + int mMode; + int mModeSet; + bool mOctetAligned; +}; + +int AmrCodec::set(int sampleRate, const char *fmtp) +{ + // These parameters are not supported. + if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") || + strcasestr(fmtp, "interleaving=")) { + return -1; + } + + // Handle mode-set and octet-align. + char *modes = strcasestr(fmtp, "mode-set="); + if (modes) { + mMode = 0; + mModeSet = 0; + for (char c = *modes; c && c != ' '; c = *++modes) { + if (c >= '0' && c <= '7') { + int mode = c - '0'; + if (mode > mMode) { + mMode = mode; + } + mModeSet |= 1 << mode; + } + } + } else { + mMode = 7; + mModeSet = 0xFF; + } + mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL); + + // TODO: handle mode-change-*. + + return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1; +} + +int AmrCodec::encode(void *payload, int16_t *samples) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + + int length = AMREncode(mEncoder, mSidSync, (Mode)mMode, + samples, bytes + 1, &type, AMR_TX_WMF); + + if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) { + return -1; + } + + if (mOctetAligned) { + bytes[0] = 0xF0; + bytes[1] = (mMode << 3) | 0x04; + ++length; + } else { + // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit). + bytes[0] = 0xFF; + bytes[1] = 0xC0 | (mMode << 1) | 1; + + // Shift left 6 bits and update the length. + bytes[length + 1] = 0; + for (int i = 0; i <= length; ++i) { + bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2); + } + length = (10 + gFrameBits[mMode] + 7) >> 3; + } + return length; +} + +int AmrCodec::decode(int16_t *samples, void *payload, int length) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + if (length < 2) { + return -1; + } + int request = bytes[0] >> 4; + + if (mOctetAligned) { + if ((bytes[1] & 0xC4) != 0x04) { + return -1; + } + type = (Frame_Type_3GPP)(bytes[1] >> 3); + if (length != (16 + gFrameBits[type] + 7) >> 3) { + return -1; + } + length -= 2; + bytes += 2; + } else { + if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) { + return -1; + } + type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07); + if (length != (10 + gFrameBits[type] + 7) >> 3) { + return -1; + } + + // Shift left 2 bits and update the length. + --length; + for (int i = 1; i < length; ++i) { + bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6); + } + bytes[length] <<= 2; + length = (gFrameBits[type] + 7) >> 3; + ++bytes; + } + + if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) { + return -1; + } + + // Handle CMR + if (request < 8 && request != mMode) { + for (int i = request; i >= 0; --i) { + if (mModeSet & (1 << i)) { + mMode = request; + break; + } + } + } + + return 160; +} + +//------------------------------------------------------------------------------ + +// See RFC 3551 for the encoding details. + class GsmEfrCodec : public AudioCodec { public: @@ -91,6 +257,11 @@ int GsmEfrCodec::decode(int16_t *samples, void *payload, int length) } // namespace +AudioCodec *newAmrCodec() +{ + return new AmrCodec; +} + AudioCodec *newGsmEfrCodec() { return new GsmEfrCodec; diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index afc193c..2267ea0 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -21,6 +21,7 @@ extern AudioCodec *newAlawCodec(); extern AudioCodec *newUlawCodec(); extern AudioCodec *newGsmCodec(); +extern AudioCodec *newAmrCodec(); extern AudioCodec *newGsmEfrCodec(); struct AudioCodecType { @@ -30,6 +31,7 @@ struct AudioCodecType { {"PCMA", newAlawCodec}, {"PCMU", newUlawCodec}, {"GSM", newGsmCodec}, + {"AMR", newAmrCodec}, {"GSM-EFR", newGsmEfrCodec}, {NULL, NULL}, }; -- cgit v1.2.3 From a072a6e3aa75c9bd038406a5067156463b58551b Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 13 Sep 2010 18:44:33 +0800 Subject: SipService: add UID check. Only allow creator or radio user to access profiles. Change-Id: I548938f117926bcc878419142d1b5d818a4e70df --- java/com/android/server/sip/SipService.java | 80 +++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 130fe9f..aff1439 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -39,6 +39,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -49,6 +50,7 @@ import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -119,17 +121,19 @@ public final class SipService extends ISipService.Stub { } public synchronized SipProfile[] getListOfProfiles() { - SipProfile[] profiles = new SipProfile[mSipGroups.size()]; - int i = 0; + boolean isCallerRadio = isCallerRadio(); + ArrayList profiles = new ArrayList(); for (SipSessionGroupExt group : mSipGroups.values()) { - profiles[i++] = group.getLocalProfile(); + if (isCallerRadio || isCallerCreator(group)) { + profiles.add(group.getLocalProfile()); + } } - return profiles; + return profiles.toArray(new SipProfile[profiles.size()]); } public void open(SipProfile localProfile) { localProfile.setCallingUid(Binder.getCallingUid()); - if (localProfile.getAutoRegistration()) { + if (localProfile.getAutoRegistration() && isCallerRadio()) { openToReceiveCalls(localProfile); } else { openToMakeCalls(localProfile); @@ -153,8 +157,14 @@ public final class SipService extends ISipService.Stub { String incomingCallBroadcastAction, ISipSessionListener listener) { localProfile.setCallingUid(Binder.getCallingUid()); if (TextUtils.isEmpty(incomingCallBroadcastAction)) { - throw new RuntimeException( - "empty broadcast action for incoming call"); + Log.w(TAG, "empty broadcast action for incoming call"); + return; + } + if (incomingCallBroadcastAction.equals( + SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) { + Log.w(TAG, "failed to open the profile; " + + "the action string is reserved"); + return; } if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + incomingCallBroadcastAction + ": " + listener); @@ -171,29 +181,64 @@ public final class SipService extends ISipService.Stub { } } + private boolean isCallerCreator(SipSessionGroupExt group) { + SipProfile profile = group.getLocalProfile(); + return (profile.getCallingUid() == Binder.getCallingUid()); + } + + private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) { + return (isCallerRadio() || isCallerCreator(group)); + } + + private boolean isCallerRadio() { + return (Binder.getCallingUid() == Process.PHONE_UID); + } + public synchronized void close(String localProfileUri) { - SipSessionGroupExt group = mSipGroups.remove(localProfileUri); - if (group != null) { - notifyProfileRemoved(group.getLocalProfile()); - group.close(); - if (isWifiOn() && !anyOpened()) releaseWifiLock(); + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + if (group == null) return; + if (!isCallerCreatorOrRadio(group)) { + Log.d(TAG, "only creator or radio can close this profile"); + return; } + + group = mSipGroups.remove(localProfileUri); + notifyProfileRemoved(group.getLocalProfile()); + group.close(); + if (isWifiOn() && !anyOpened()) releaseWifiLock(); } public synchronized boolean isOpened(String localProfileUri) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isOpened() : false); + if (group == null) return false; + if (isCallerCreatorOrRadio(group)) { + return group.isOpened(); + } else { + Log.i(TAG, "only creator or radio can query on the profile"); + return false; + } } public synchronized boolean isRegistered(String localProfileUri) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - return ((group != null) ? group.isRegistered() : false); + if (group == null) return false; + if (isCallerCreatorOrRadio(group)) { + return group.isRegistered(); + } else { + Log.i(TAG, "only creator or radio can query on the profile"); + return false; + } } public synchronized void setRegistrationListener(String localProfileUri, ISipSessionListener listener) { SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group != null) group.setListener(listener); + if (group == null) return; + if (isCallerCreator(group)) { + group.setListener(listener); + } else { + Log.i(TAG, "only creator can set listener on the profile"); + } } public synchronized ISipSession createSession(SipProfile localProfile, @@ -234,6 +279,8 @@ public final class SipService extends ISipService.Stub { group = new SipSessionGroupExt(localProfile, null, null); mSipGroups.put(key, group); notifyProfileAdded(localProfile); + } else if (!isCallerCreator(group)) { + throw new SipException("only creator can access the profile"); } return group; } @@ -244,6 +291,9 @@ public final class SipService extends ISipService.Stub { String key = localProfile.getUriString(); SipSessionGroupExt group = mSipGroups.get(key); if (group != null) { + if (!isCallerCreator(group)) { + throw new SipException("only creator can access the profile"); + } group.setIncomingCallBroadcastAction( incomingCallBroadcastAction); group.setListener(listener); -- cgit v1.2.3 From b727dc4e022dd5a64301dffc527db92e023bf156 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 30 Sep 2010 13:48:07 +0800 Subject: RTP: Adjust the jitter buffer to 512ms. Change-Id: Ia91c1aa1a03b65dbd329ea98383f370844e2b0c0 --- jni/rtp/AudioGroup.cpp | 58 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index f09edb6..a1caa1f 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -57,9 +57,9 @@ int gRandom = -1; // a modulo operation on the index while accessing the array. However modulo can // be expensive on some platforms, such as ARM. Thus we round up the size of the // array to the nearest power of 2 and then use bitwise-and instead of modulo. -// Currently we make it 256ms long and assume packet interval is 32ms or less. -// The first 64ms is the place where samples get mixed. The rest 192ms is the -// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers +// Currently we make it 512ms long and assume packet interval is 40ms or less. +// The first 80ms is the place where samples get mixed. The rest 432ms is the +// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers // are chosen by experiments and each of them can be adjusted as needed. // Other notes: @@ -69,7 +69,11 @@ int gRandom = -1; // milliseconds. No floating points. // + If we cannot get enough CPU, we drop samples and simulate packet loss. // + Resampling is not done yet, so streams in one group must use the same rate. -// For the first release we might only support 8kHz and 16kHz. +// For the first release only 8000Hz is supported. + +#define BUFFER_SIZE 512 +#define HISTORY_SIZE 80 +#define MEASURE_PERIOD 2000 class AudioStream { @@ -111,6 +115,7 @@ private: int mBufferMask; int mBufferHead; int mBufferTail; + int mLatencyTimer; int mLatencyScore; uint16_t mSequence; @@ -159,12 +164,13 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, mInterval = mSampleCount / mSampleRate; // Allocate jitter buffer. - for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1); - mBufferMask >>= 2; + for (mBufferMask = 8; mBufferMask < mSampleRate; mBufferMask <<= 1); + mBufferMask *= BUFFER_SIZE; mBuffer = new int16_t[mBufferMask]; --mBufferMask; mBufferHead = 0; mBufferTail = 0; + mLatencyTimer = 0; mLatencyScore = 0; // Initialize random bits. @@ -340,29 +346,35 @@ void AudioStream::decode(int tick) } // Make sure mBufferHead and mBufferTail are reasonable. - if ((unsigned int)(tick + 256 - mBufferHead) > 1024) { - mBufferHead = tick - 64; + if ((unsigned int)(tick + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) { + mBufferHead = tick - HISTORY_SIZE; mBufferTail = mBufferHead; } - if (tick - mBufferHead > 64) { + if (tick - mBufferHead > HISTORY_SIZE) { // Throw away outdated samples. - mBufferHead = tick - 64; + mBufferHead = tick - HISTORY_SIZE; if (mBufferTail - mBufferHead < 0) { mBufferTail = mBufferHead; } } - if (mBufferTail - tick <= 80) { - mLatencyScore = tick; - } else if (tick - mLatencyScore >= 5000) { - // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms - // in the past 5s. This rarely happens, so let us just keep it simple. - LOGD("stream[%d] latency control", mSocket); - mBufferTail = tick + 40; + // Adjust the jitter buffer if the latency keeps larger than two times of the + // packet interval in the past two seconds. + int score = mBufferTail - tick - mInterval * 2; + if (mLatencyScore > score) { + mLatencyScore = score; + } + if (mLatencyScore <= 0) { + mLatencyTimer = tick; + mLatencyScore = score; + } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { + LOGD("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + mBufferTail -= mLatencyScore; + mLatencyTimer = tick; } - if (mBufferTail - mBufferHead > 256 - mInterval) { + if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) { // Buffer overflow. Drop the packet. LOGD("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); @@ -413,17 +425,17 @@ void AudioStream::decode(int tick) } if (tick - mBufferTail > 0) { - // Buffer underrun. Reset the jitter buffer to 40ms. + // Buffer underrun. Reset the jitter buffer. LOGD("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { - mBufferHead = tick + 40; + mBufferHead = tick + mInterval; mBufferTail = mBufferHead; } else { - int tail = (tick + 40) * mSampleRate; + int tail = (tick + mInterval) * mSampleRate; for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) { mBuffer[i & mBufferMask] = 0; } - mBufferTail = tick + 40; + mBufferTail = tick + mInterval; } } @@ -680,7 +692,7 @@ bool AudioGroup::NetworkThread::threadLoop() int count = 0; for (AudioStream *stream = chain; stream; stream = stream->mNext) { - if (!stream->mTick || tick - stream->mTick >= 0) { + if (tick - stream->mTick >= 0) { stream->encode(tick, chain); } if (deadline - stream->mTick > 0) { -- cgit v1.2.3 From f3d1e1fd0f86eb956c82634c075b167dc367757c Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 30 Sep 2010 15:00:34 +0800 Subject: Add uri field to SipManager.ListenerRelay in case mSession is not available. Change-Id: Ifee2c129e48aa1177f648f176413ab6aa5606770 --- java/android/net/sip/SipManager.java | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 52f5716..a589fe9 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -173,7 +173,7 @@ public class SipManager { SipRegistrationListener listener) throws SipException { try { mSipService.open3(localProfile, incomingCallBroadcastAction, - createRelay(listener)); + createRelay(listener, localProfile.getUriString())); } catch (RemoteException e) { throw new SipException("open()", e); } @@ -191,7 +191,7 @@ public class SipManager { SipRegistrationListener listener) throws SipException { try { mSipService.setRegistrationListener( - localProfileUri, createRelay(listener)); + localProfileUri, createRelay(listener, localProfileUri)); } catch (RemoteException e) { throw new SipException("setRegistrationListener()", e); } @@ -425,8 +425,8 @@ public class SipManager { public void register(SipProfile localProfile, int expiryTime, SipRegistrationListener listener) throws SipException { try { - ISipSession session = mSipService.createSession( - localProfile, createRelay(listener)); + ISipSession session = mSipService.createSession(localProfile, + createRelay(listener, localProfile.getUriString())); session.register(expiryTime); } catch (RemoteException e) { throw new SipException("register()", e); @@ -446,8 +446,8 @@ public class SipManager { public void unregister(SipProfile localProfile, SipRegistrationListener listener) throws SipException { try { - ISipSession session = mSipService.createSession( - localProfile, createRelay(listener)); + ISipSession session = mSipService.createSession(localProfile, + createRelay(listener, localProfile.getUriString())); session.unregister(); } catch (RemoteException e) { throw new SipException("unregister()", e); @@ -475,8 +475,8 @@ public class SipManager { } private static ISipSessionListener createRelay( - SipRegistrationListener listener) { - return ((listener == null) ? null : new ListenerRelay(listener)); + SipRegistrationListener listener, String uri) { + return ((listener == null) ? null : new ListenerRelay(listener, uri)); } /** @@ -512,16 +512,18 @@ public class SipManager { private static class ListenerRelay extends SipSessionAdapter { private SipRegistrationListener mListener; + private String mUri; // listener must not be null - public ListenerRelay(SipRegistrationListener listener) { + public ListenerRelay(SipRegistrationListener listener, String uri) { mListener = listener; + mUri = uri; } private String getUri(ISipSession session) { try { return ((session == null) - ? "no session" + ? mUri : session.getLocalProfile().getUriString()); } catch (RemoteException e) { throw new RuntimeException(e); -- cgit v1.2.3 From d85beac45c12a65076a1c157681de9a5b03bdec7 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 30 Sep 2010 16:07:44 +0800 Subject: RTP: Minor fixes with polishing. Change-Id: I50641373989e512fb489b5017edbcfd7848fe8b9 --- jni/rtp/AudioGroup.cpp | 28 ++++++++++++++++------------ jni/rtp/G711Codec.cpp | 2 +- jni/rtp/RtpStream.cpp | 9 +++------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index a1caa1f..a953d38 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -89,7 +89,6 @@ public: void encode(int tick, AudioStream *chain); void decode(int tick); -private: enum { NORMAL = 0, SEND_ONLY = 1, @@ -97,6 +96,7 @@ private: LAST_MODE = 2, }; +private: int mMode; int mSocket; sockaddr_storage mRemote; @@ -202,8 +202,8 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, } } - LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, - (codec ? codec->name : "RAW"), mSampleRate, mInterval); + LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, + (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode); return true; } @@ -253,7 +253,7 @@ void AudioStream::encode(int tick, AudioStream *chain) mTick += skipped * mInterval; mSequence += skipped; mTimestamp += skipped * mSampleCount; - LOGD("stream[%d] skips %d packets", mSocket, skipped); + LOGV("stream[%d] skips %d packets", mSocket, skipped); } tick = mTick; @@ -302,7 +302,7 @@ void AudioStream::encode(int tick, AudioStream *chain) if (!mixed) { if ((mTick ^ mLogThrottle) >> 10) { mLogThrottle = mTick; - LOGD("stream[%d] no data", mSocket); + LOGV("stream[%d] no data", mSocket); } return; } @@ -330,7 +330,7 @@ void AudioStream::encode(int tick, AudioStream *chain) buffer[2] = mSsrc; int length = mCodec->encode(&buffer[3], samples); if (length <= 0) { - LOGD("stream[%d] encoder error", mSocket); + LOGV("stream[%d] encoder error", mSocket); return; } sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, @@ -369,14 +369,14 @@ void AudioStream::decode(int tick) mLatencyTimer = tick; mLatencyScore = score; } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { - LOGD("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); mBufferTail -= mLatencyScore; mLatencyTimer = tick; } if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) { // Buffer overflow. Drop the packet. - LOGD("stream[%d] buffer overflow", mSocket); + LOGV("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); return; } @@ -400,7 +400,7 @@ void AudioStream::decode(int tick) // reliable but at least they can be used to identify duplicates? if (length < 12 || length > (int)sizeof(buffer) || (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { - LOGD("stream[%d] malformed packet", mSocket); + LOGV("stream[%d] malformed packet", mSocket); return; } int offset = 12 + ((buffer[0] & 0x0F) << 2); @@ -420,13 +420,13 @@ void AudioStream::decode(int tick) } } if (length <= 0) { - LOGD("stream[%d] decoder error", mSocket); + LOGV("stream[%d] decoder error", mSocket); return; } if (tick - mBufferTail > 0) { // Buffer underrun. Reset the jitter buffer. - LOGD("stream[%d] buffer underrun", mSocket); + LOGV("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { mBufferHead = tick + mInterval; mBufferTail = mBufferHead; @@ -462,7 +462,6 @@ public: bool add(AudioStream *stream); bool remove(int socket); -private: enum { ON_HOLD = 0, MUTED = 1, @@ -471,6 +470,7 @@ private: LAST_MODE = 3, }; +private: AudioStream *mChain; int mEventQueue; volatile int mDtmfEvent; @@ -946,6 +946,10 @@ void remove(JNIEnv *env, jobject thiz, jint socket) void setMode(JNIEnv *env, jobject thiz, jint mode) { + if (mode < 0 || mode > AudioGroup::LAST_MODE) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); if (group && !group->setMode(mode)) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); diff --git a/jni/rtp/G711Codec.cpp b/jni/rtp/G711Codec.cpp index 091afa9..a467acf 100644 --- a/jni/rtp/G711Codec.cpp +++ b/jni/rtp/G711Codec.cpp @@ -18,7 +18,7 @@ namespace { -int8_t gExponents[128] = { +const int8_t gExponents[128] = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, diff --git a/jni/rtp/RtpStream.cpp b/jni/rtp/RtpStream.cpp index 33b88e4..f5efc17 100644 --- a/jni/rtp/RtpStream.cpp +++ b/jni/rtp/RtpStream.cpp @@ -88,13 +88,11 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) jint dup(JNIEnv *env, jobject thiz) { - int socket1 = env->GetIntField(thiz, gNative); - int socket2 = ::dup(socket1); - if (socket2 == -1) { + int socket = ::dup(env->GetIntField(thiz, gNative)); + if (socket == -1) { jniThrowException(env, "java/lang/IllegalStateException", strerror(errno)); } - LOGD("dup %d to %d", socket1, socket2); - return socket2; + return socket; } void close(JNIEnv *env, jobject thiz) @@ -102,7 +100,6 @@ void close(JNIEnv *env, jobject thiz) int socket = env->GetIntField(thiz, gNative); ::close(socket); env->SetIntField(thiz, gNative, -1); - LOGD("close %d", socket); } JNINativeMethod gMethods[] = { -- cgit v1.2.3 From e8fd118a45075a653dddfd21b37394c63e513954 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 1 Oct 2010 07:09:30 +0800 Subject: SipService: turn off verbose logging Change-Id: I264662ba17d215d532f58b6ee793e569fe67c334 --- java/com/android/server/sip/SipService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index aff1439..405dff8 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -66,6 +66,7 @@ import javax.sip.SipException; */ public final class SipService extends ISipService.Stub { private static final String TAG = "SipService"; + private static final boolean DEBUGV = false; private static final boolean DEBUG = true; private static final boolean DEBUG_TIMER = DEBUG && false; private static final int EXPIRY_TIME = 3600; @@ -597,7 +598,7 @@ public final class SipService extends ISipService.Stub { if (notCurrentSession(session)) return; session = session.duplicate(); - if (DEBUG) Log.d(TAG, "~~~ keepalive"); + if (DEBUGV) Log.v(TAG, "~~~ keepalive"); mTimer.cancel(this); session.sendKeepAlive(); if (session.isReRegisterRequired()) { -- cgit v1.2.3 From 0ec517b89eef54d900ed1c49c2611707d2406d3b Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Fri, 1 Oct 2010 08:20:09 +0800 Subject: RTP: Start AudioRecord before AudioTrack to avoid being disabled. Change-Id: I96be89fda41d77e2cf5bfc1c2f14e2b109001b57 --- jni/rtp/AudioGroup.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index a953d38..5214518 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -776,11 +776,14 @@ bool AudioGroup::DeviceThread::threadLoop() char c; while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); - // Start your engine! - track.start(); + // Start AudioRecord before AudioTrack. This prevents AudioTrack from being + // disabled due to buffer underrun while waiting for AudioRecord. if (mode != MUTED) { record.start(); + int16_t one; + record.read(&one, sizeof(one)); } + track.start(); while (!exitPending()) { int16_t output[sampleCount]; @@ -806,34 +809,30 @@ bool AudioGroup::DeviceThread::threadLoop() track.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { LOGE("cannot write to AudioTrack"); - break; + return true; } } if (toRead > 0) { AudioRecord::Buffer buffer; - buffer.frameCount = record.frameCount(); + buffer.frameCount = toRead; status_t status = record.obtainBuffer(&buffer, 1); if (status == NO_ERROR) { - int count = ((int)buffer.frameCount < toRead) ? - buffer.frameCount : toRead; - memcpy(&input[sampleCount - toRead], buffer.i8, count * 2); - toRead -= count; - if (buffer.frameCount < record.frameCount()) { - buffer.frameCount = count; - } + int offset = sampleCount - toRead; + memcpy(&input[offset], buffer.i8, buffer.size); + toRead -= buffer.frameCount; record.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { LOGE("cannot read from AudioRecord"); - break; + return true; } } } if (chances <= 0) { - LOGE("device loop timeout"); - break; + LOGW("device loop timeout"); + while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); } if (mode != MUTED) { -- cgit v1.2.3 From e8782e2fdfd256296a0d1fd2a1c3f98d58103eaa Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sun, 3 Oct 2010 20:35:02 +0800 Subject: SIP: minor fixes. + Log error instead of crashing app process in SipManager's ListenerRelay. + Terminate dialog and transaction in SipSessionGroup.reset(). + Remove redundant reset() in SipSessionGroup. Change-Id: Ifbf29d2c9607ffe1a1a50b0c131ee3a4e81a0d0e --- java/android/net/sip/SipManager.java | 9 +++++++-- java/com/android/server/sip/SipSessionGroup.java | 23 ++++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index a589fe9..bd859e8 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -23,6 +23,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.util.Log; import java.text.ParseException; @@ -83,6 +84,8 @@ public class SipManager { /** Part of the incoming call intent. */ public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; + private static final String TAG = "SipManager"; + private ISipService mSipService; private Context mContext; @@ -525,8 +528,10 @@ public class SipManager { return ((session == null) ? mUri : session.getLocalProfile().getUriString()); - } catch (RemoteException e) { - throw new RuntimeException(e); + } catch (Throwable e) { + // SipService died? SIP stack died? + Log.w(TAG, "getUri(): " + e); + return null; } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index c68fa1b..bc377cf 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -49,6 +49,7 @@ import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; +import javax.sip.ObjectInUseException; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.ServerTransaction; @@ -415,10 +416,24 @@ class SipSessionGroup implements SipListener { mPeerProfile = null; mState = SipSession.State.READY_TO_CALL; mInviteReceived = null; + mPeerSessionDescription = null; + + if (mDialog != null) mDialog.delete(); mDialog = null; + + try { + if (mServerTransaction != null) mServerTransaction.terminate(); + } catch (ObjectInUseException e) { + // ignored + } mServerTransaction = null; + + try { + if (mClientTransaction != null) mClientTransaction.terminate(); + } catch (ObjectInUseException e) { + // ignored + } mClientTransaction = null; - mPeerSessionDescription = null; cancelSessionTimer(); } @@ -884,8 +899,8 @@ class SipSessionGroup implements SipListener { generateTag()); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mProxy.onCalling(this); startSessionTimer(cmd.getTimeout()); + mProxy.onCalling(this); return true; } else if (evt instanceof RegisterCommand) { mState = SipSession.State.REGISTERING; @@ -964,8 +979,8 @@ class SipSessionGroup implements SipListener { // ring back for better UX if (mState == SipSession.State.OUTGOING_CALL) { mState = SipSession.State.OUTGOING_CALL_RING_BACK; - mProxy.onRingingBack(this); cancelSessionTimer(); + mProxy.onRingingBack(this); } return true; case Response.OK: @@ -1222,14 +1237,12 @@ class SipSessionGroup implements SipListener { } private void onRegistrationFailed(Throwable exception) { - reset(); exception = getRootCause(exception); onRegistrationFailed(getErrorCode(exception), exception.toString()); } private void onRegistrationFailed(Response response) { - reset(); int statusCode = response.getStatusCode(); onRegistrationFailed(getErrorCode(statusCode), createErrorMessage(response)); -- cgit v1.2.3 From c7092775516e15983053a58b3b43ff3eb58ac46f Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 5 Oct 2010 01:17:13 +0800 Subject: RTP: Add a baseline echo suppressor. Change-Id: I832f1f572f141fd928afe671b12d0b59f2a8e0b1 --- jni/rtp/Android.mk | 1 + jni/rtp/AudioGroup.cpp | 7 +- jni/rtp/EchoSuppressor.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++++ jni/rtp/EchoSuppressor.h | 51 ++++++++++++++ 4 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 jni/rtp/EchoSuppressor.cpp create mode 100644 jni/rtp/EchoSuppressor.h diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 5909c0d..76c43ba 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -22,6 +22,7 @@ LOCAL_MODULE := librtp_jni LOCAL_SRC_FILES := \ AudioCodec.cpp \ AudioGroup.cpp \ + EchoSuppressor.cpp \ RtpStream.cpp \ util.cpp \ rtp_jni.cpp diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 5214518..9da560a 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -44,6 +44,7 @@ #include "JNIHelp.h" #include "AudioCodec.h" +#include "EchoSuppressor.h" extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); @@ -766,7 +767,9 @@ bool AudioGroup::DeviceThread::threadLoop() } LOGD("latency: output %d, input %d", track.latency(), record.latency()); - // TODO: initialize echo canceler here. + // Initialize echo canceler. + EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 + + (track.latency() + record.latency()) * sampleRate / 1000); // Give device socket a reasonable buffer size. setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); @@ -839,7 +842,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (mode == NORMAL) { send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); } else { - // TODO: Echo canceller runs here. + echo.run(output, input); send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); } } diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp new file mode 100644 index 0000000..92015a9 --- /dev/null +++ b/jni/rtp/EchoSuppressor.cpp @@ -0,0 +1,171 @@ +/* + * Copyrightm (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. + */ + +#include +#include +#include + +#define LOG_TAG "Echo" +#include + +#include "EchoSuppressor.h" + +EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength) +{ + int scale = 1; + while (tailLength > 200 * scale) { + scale <<= 1; + } + if (scale > sampleCount) { + scale = sampleCount; + } + + mScale = scale; + mSampleCount = sampleCount; + mWindowSize = sampleCount / scale; + mTailLength = (tailLength + scale - 1) / scale; + mRecordLength = (sampleRate + sampleCount - 1) / sampleCount; + mRecordOffset = 0; + + mXs = new float[mTailLength + mWindowSize]; + memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize)); + mXYs = new float[mTailLength]; + memset(mXYs, 0, sizeof(float) * mTailLength); + mXXs = new float[mTailLength]; + memset(mXYs, 0, sizeof(float) * mTailLength); + mYY = 0; + + mXYRecords = new float[mRecordLength * mTailLength]; + memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength); + mXXRecords = new float[mRecordLength * mWindowSize]; + memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize); + mYYRecords = new float[mRecordLength]; + memset(mYYRecords, 0, sizeof(float) * mRecordLength); + + mLastX = 0; + mLastY = 0; +} + +EchoSuppressor::~EchoSuppressor() +{ + delete [] mXs; + delete [] mXYs; + delete [] mXXs; + delete [] mXYRecords; + delete [] mXXRecords; + delete [] mYYRecords; +} + +void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) +{ + float *records; + + // Update Xs. + for (int i = 0; i < mTailLength; ++i) { + mXs[i] = mXs[mWindowSize + i]; + } + for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) { + float sum = 0; + for (int k = 0; k < mScale; ++k) { + float x = playbacked[j + k] >> 8; + mLastX += x; + sum += (mLastX >= 0) ? mLastX : -mLastX; + mLastX = 0.005f * mLastX - x; + } + mXs[mTailLength - 1 + i] = sum; + } + + // Update XXs and XXRecords. + for (int i = 0; i < mTailLength - mWindowSize; ++i) { + mXXs[i] = mXXs[mWindowSize + i]; + } + records = &mXXRecords[mRecordOffset * mWindowSize]; + for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) { + float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i]; + mXXs[j] = mXXs[j - 1] + xx - records[i]; + records[i] = xx; + if (mXXs[j] < 0) { + mXXs[j] = 0; + } + } + + // Compute Ys. + float ys[mWindowSize]; + for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) { + float sum = 0; + for (int k = 0; k < mScale; ++k) { + float y = recorded[j + k] >> 8; + mLastY += y; + sum += (mLastY >= 0) ? mLastY : -mLastY; + mLastY = 0.005f * mLastY - y; + } + ys[i] = sum; + } + + // Update YY and YYRecords. + float yy = 0; + for (int i = 0; i < mWindowSize; ++i) { + yy += ys[i] * ys[i]; + } + mYY += yy - mYYRecords[mRecordOffset]; + mYYRecords[mRecordOffset] = yy; + if (mYY < 0) { + mYY = 0; + } + + // Update XYs and XYRecords. + records = &mXYRecords[mRecordOffset * mTailLength]; + for (int i = 0; i < mTailLength; ++i) { + float xy = 0; + for (int j = 0;j < mWindowSize; ++j) { + xy += mXs[i + j] * ys[j]; + } + mXYs[i] += xy - records[i]; + records[i] = xy; + if (mXYs[i] < 0) { + mXYs[i] = 0; + } + } + + // Computes correlations from XYs, XXs, and YY. + float weight = 1.0f / (mYY + 1); + float correlation = 0; + int latency = 0; + for (int i = 0; i < mTailLength; ++i) { + float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1); + if (c > correlation) { + correlation = c; + latency = i; + } + } + + correlation = sqrtf(correlation); + if (correlation > 0.3f) { + float factor = 1.0f - correlation; + factor *= factor; + for (int i = 0; i < mSampleCount; ++i) { + recorded[i] *= factor; + } + } +// LOGI("latency %5d, correlation %.10f", latency, correlation); + + + // Increase RecordOffset. + ++mRecordOffset; + if (mRecordOffset == mRecordLength) { + mRecordOffset = 0; + } +} diff --git a/jni/rtp/EchoSuppressor.h b/jni/rtp/EchoSuppressor.h new file mode 100644 index 0000000..85decf5 --- /dev/null +++ b/jni/rtp/EchoSuppressor.h @@ -0,0 +1,51 @@ +/* + * Copyrightm (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. + */ + +#ifndef __ECHO_SUPPRESSOR_H__ +#define __ECHO_SUPPRESSOR_H__ + +#include + +class EchoSuppressor +{ +public: + // The sampleCount must be power of 2. + EchoSuppressor(int sampleRate, int sampleCount, int tailLength); + ~EchoSuppressor(); + void run(int16_t *playbacked, int16_t *recorded); + +private: + int mScale; + int mSampleCount; + int mWindowSize; + int mTailLength; + int mRecordLength; + int mRecordOffset; + + float *mXs; + float *mXYs; + float *mXXs; + float mYY; + + float *mXYRecords; + float *mXXRecords; + float *mYYRecords; + + float mLastX; + float mLastY; +}; + +#endif -- cgit v1.2.3 From 845f7332f04864c5483b3e63da5db076fc7a888a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 5 Oct 2010 09:35:38 +0800 Subject: SipService: supply PendingIntent when open a profile. The SipService used to take an action string and broadcasts an intent with that action string when an incoming call is received. The design is not safe (as the intent may be sniffed) and inflexible (can only received by BroadcastReceiver). Now we use PendingIntent to fix all these. Companion CL: https://android-git.corp.google.com/g/#change,71800 Change-Id: Id12e5c1cf9321edafb171494932cd936eae10b6e --- java/android/net/sip/ISipService.aidl | 3 +- java/android/net/sip/SipManager.java | 62 ++++++++++++++++-------- java/com/android/server/sip/SipService.java | 73 ++++++++++++----------------- 3 files changed, 74 insertions(+), 64 deletions(-) diff --git a/java/android/net/sip/ISipService.aidl b/java/android/net/sip/ISipService.aidl index 6c68213..3250bf9 100644 --- a/java/android/net/sip/ISipService.aidl +++ b/java/android/net/sip/ISipService.aidl @@ -16,6 +16,7 @@ package android.net.sip; +import android.app.PendingIntent; import android.net.sip.ISipSession; import android.net.sip.ISipSessionListener; import android.net.sip.SipProfile; @@ -26,7 +27,7 @@ import android.net.sip.SipProfile; interface ISipService { void open(in SipProfile localProfile); void open3(in SipProfile localProfile, - String incomingCallBroadcastAction, + in PendingIntent incomingCallPendingIntent, in ISipSessionListener listener); void close(in String localProfileUri); boolean isOpened(String localProfileUri); diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index bd859e8..80c35fb 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -16,6 +16,7 @@ package android.net.sip; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -34,7 +35,7 @@ import java.text.ParseException; *
  • 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, PendingIntent, SipRegistrationListener)}, * {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and * {@link #isRegistered}. It also facilitates handling of the incoming call * broadcast intent. See @@ -50,6 +51,19 @@ import java.text.ParseException; * @hide */ public class SipManager { + /** + * The result code to be sent back with the incoming call + * {@link PendingIntent}. + * @see #open(SipProfile, PendingIntent, SipRegistrationListener) + */ + public static final int INCOMING_CALL_RESULT_CODE = 101; + + /** 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"; + /** * Action string for the incoming call intent for the Phone app. * Internal use only. @@ -78,12 +92,6 @@ public class SipManager { */ public static final String EXTRA_LOCAL_URI = "android:localSipUri"; - /** 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 static final String TAG = "SipManager"; private ISipService mSipService; @@ -142,7 +150,8 @@ public class SipManager { /** * 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)} + * profile, use + * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} * instead. * * @param localProfile the SIP profile to make calls from @@ -165,17 +174,29 @@ public class SipManager { * 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 - * incoming call is received + * @param incomingCallPendingIntent When an incoming call is received, the + * SIP service will call + * {@link PendingIntent#send(Context, int, Intent)} to send back the + * intent to the caller with {@link #INCOMING_CALL_RESULT_CODE} as the + * result code and the intent to fill in the call ID and session + * description information. It cannot be null. * @param listener to listen to registration events; can be null + * @see #getCallId + * @see #getOfferSessionDescription + * @see #takeAudioCall + * @throws NullPointerException if {@code incomingCallPendingIntent} is null * @throws SipException if the profile contains incorrect settings or * calling the SIP service results in an error */ public void open(SipProfile localProfile, - String incomingCallBroadcastAction, + PendingIntent incomingCallPendingIntent, SipRegistrationListener listener) throws SipException { + if (incomingCallPendingIntent == null) { + throw new NullPointerException( + "incomingCallPendingIntent cannot be null"); + } try { - mSipService.open3(localProfile, incomingCallBroadcastAction, + mSipService.open3(localProfile, incomingCallPendingIntent, createRelay(listener, localProfile.getUriString())); } catch (RemoteException e) { throw new SipException("open()", e); @@ -184,7 +205,8 @@ public class SipManager { /** * Sets the listener to listen to registration events. No effect if the - * profile has not been opened to receive calls (see {@link #open}). + * profile has not been opened to receive calls (see + * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)}). * * @param localProfileUri the URI of the profile * @param listener to listen to registration events; can be null @@ -282,9 +304,9 @@ public class SipManager { } /** - * Creates a {@link SipAudioCall} to make a call. To use this method, one - * must call {@link #open(SipProfile)} first. The attempt will be timed out - * if the call is not established within {@code timeout} seconds and + * Creates a {@link SipAudioCall} to make an audio call. The attempt will be + * timed out if the call is not established within {@code timeout} seconds + * and * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * @@ -416,9 +438,11 @@ public class SipManager { /** * 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. + * receiving calls. + * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is + * still needed to be called at least once in order for the SIP service to + * notify the caller with the {@code PendingIntent} when an incoming call is + * received. * * @param localProfile the SIP profile to register with * @param expiryTime registration expiration time (in seconds) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 405dff8..a7c61e5 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -134,14 +134,6 @@ public final class SipService extends ISipService.Stub { public void open(SipProfile localProfile) { localProfile.setCallingUid(Binder.getCallingUid()); - if (localProfile.getAutoRegistration() && isCallerRadio()) { - openToReceiveCalls(localProfile); - } else { - openToMakeCalls(localProfile); - } - } - - private void openToMakeCalls(SipProfile localProfile) { try { createGroup(localProfile); } catch (SipException e) { @@ -150,28 +142,20 @@ public final class SipService extends ISipService.Stub { } } - private void openToReceiveCalls(SipProfile localProfile) { - open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null); - } - public synchronized void open3(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) { + PendingIntent incomingCallPendingIntent, + ISipSessionListener listener) { localProfile.setCallingUid(Binder.getCallingUid()); - if (TextUtils.isEmpty(incomingCallBroadcastAction)) { - Log.w(TAG, "empty broadcast action for incoming call"); - return; - } - if (incomingCallBroadcastAction.equals( - SipManager.ACTION_SIP_INCOMING_CALL) && !isCallerRadio()) { - Log.w(TAG, "failed to open the profile; " - + "the action string is reserved"); + if (incomingCallPendingIntent == null) { + Log.w(TAG, "incomingCallPendingIntent cannot be null; " + + "the profile is not opened"); return; } if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " - + incomingCallBroadcastAction + ": " + listener); + + incomingCallPendingIntent + ": " + listener); try { SipSessionGroupExt group = createGroup(localProfile, - incomingCallBroadcastAction, listener); + incomingCallPendingIntent, listener); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); if (isWifiOn()) grabWifiLock(); @@ -287,20 +271,19 @@ public final class SipService extends ISipService.Stub { } private SipSessionGroupExt createGroup(SipProfile localProfile, - String incomingCallBroadcastAction, ISipSessionListener listener) - throws SipException { + PendingIntent incomingCallPendingIntent, + ISipSessionListener listener) throws SipException { String key = localProfile.getUriString(); SipSessionGroupExt group = mSipGroups.get(key); if (group != null) { if (!isCallerCreator(group)) { throw new SipException("only creator can access the profile"); } - group.setIncomingCallBroadcastAction( - incomingCallBroadcastAction); + group.setIncomingCallPendingIntent(incomingCallPendingIntent); group.setListener(listener); } else { group = new SipSessionGroupExt(localProfile, - incomingCallBroadcastAction, listener); + incomingCallPendingIntent, listener); mSipGroups.put(key, group); notifyProfileAdded(localProfile); } @@ -405,19 +388,19 @@ public final class SipService extends ISipService.Stub { private class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; - private String mIncomingCallBroadcastAction; + private PendingIntent mIncomingCallPendingIntent; private boolean mOpened; private AutoRegistrationProcess mAutoRegistration = new AutoRegistrationProcess(); public SipSessionGroupExt(SipProfile localProfile, - String incomingCallBroadcastAction, + PendingIntent incomingCallPendingIntent, ISipSessionListener listener) throws SipException { String password = localProfile.getPassword(); SipProfile p = duplicate(localProfile); mSipGroup = createSipSessionGroup(mLocalIp, p, password); - mIncomingCallBroadcastAction = incomingCallBroadcastAction; + mIncomingCallPendingIntent = incomingCallPendingIntent; mAutoRegistration.setListener(listener); } @@ -458,8 +441,8 @@ public final class SipService extends ISipService.Stub { mAutoRegistration.setListener(listener); } - public void setIncomingCallBroadcastAction(String action) { - mIncomingCallBroadcastAction = action; + public void setIncomingCallPendingIntent(PendingIntent pIntent) { + mIncomingCallPendingIntent = pIntent; } public void openToReceiveCalls() throws SipException { @@ -469,7 +452,7 @@ public final class SipService extends ISipService.Stub { mAutoRegistration.start(mSipGroup); } if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " - + mIncomingCallBroadcastAction); + + mIncomingCallPendingIntent); } public void onConnectivityChanged(boolean connected) @@ -481,7 +464,7 @@ public final class SipService extends ISipService.Stub { } else { // close mSipGroup but remember mOpened if (DEBUG) Log.d(TAG, " close auto reg temporarily: " - + getUri() + ": " + mIncomingCallBroadcastAction); + + getUri() + ": " + mIncomingCallPendingIntent); mSipGroup.close(); mAutoRegistration.stop(); } @@ -508,7 +491,7 @@ public final class SipService extends ISipService.Stub { mSipGroup.close(); mAutoRegistration.stop(); if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " - + mIncomingCallBroadcastAction); + + mIncomingCallPendingIntent); } public ISipSession createSession(ISipSessionListener listener) { @@ -516,8 +499,10 @@ public final class SipService extends ISipService.Stub { } @Override - public void onRinging(ISipSession session, SipProfile caller, + public void onRinging(ISipSession s, SipProfile caller, String sessionDescription) { + SipSessionGroup.SipSessionImpl session = + (SipSessionGroup.SipSessionImpl) s; synchronized (SipService.this) { try { if (!isRegistered()) { @@ -528,15 +513,15 @@ public final class SipService extends ISipService.Stub { // send out incoming call broadcast addPendingSession(session); Intent intent = SipManager.createIncomingCallBroadcast( - session.getCallId(), sessionDescription) - .setAction(mIncomingCallBroadcastAction); + session.getCallId(), sessionDescription); if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " + caller.getUri() + ": " + session.getCallId() - + " " + mIncomingCallBroadcastAction); - mContext.sendBroadcast(intent); - } catch (RemoteException e) { - // should never happen with a local call - Log.e(TAG, "processCall()", e); + + " " + mIncomingCallPendingIntent); + mIncomingCallPendingIntent.send(mContext, + SipManager.INCOMING_CALL_RESULT_CODE, intent); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "pendingIntent is canceled, drop incoming call"); + session.endCall(); } } } -- cgit v1.2.3 From e26eb3274a65c41a6a30bdace1818c5629cca1c8 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 5 Oct 2010 13:00:13 +0800 Subject: SIP: add SERVER_UNREACHABLE error code. Let SipSession return it when UnknownHostException is caught. Add DisconnectCause.SERVER_UNREACHABLE in Connection and have SipPhone report it when receiving SERVER_UNREACHABLE from SipSession. http://b/issue?id=3061691 Change-Id: I944328ba3ee30c0a9386e89b5c4696d4d9bde000 --- java/android/net/sip/SipErrorCode.java | 5 +++++ java/com/android/server/sip/SipSessionGroup.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index eb7a1ae..a55ab25 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -61,6 +61,9 @@ public class SipErrorCode { /** Cross-domain authentication required. */ public static final int CROSS_DOMAIN_AUTHENTICATION = -11; + /** When the server is not reachable. */ + public static final int SERVER_UNREACHABLE = -12; + public static String toString(int errorCode) { switch (errorCode) { case NO_ERROR: @@ -87,6 +90,8 @@ public class SipErrorCode { return "DATA_CONNECTION_LOST"; case CROSS_DOMAIN_AUTHENTICATION: return "CROSS_DOMAIN_AUTHENTICATION"; + case SERVER_UNREACHABLE: + return "SERVER_UNREACHABLE"; default: return "UNKNOWN"; } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index bc377cf..37fffa8 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -480,7 +480,7 @@ class SipSessionGroup implements SipListener { public void run() { try { processCommand(command); - } catch (SipException e) { + } catch (Throwable e) { Log.w(TAG, "command error: " + command, e); onError(e); } @@ -1218,7 +1218,7 @@ class SipSessionGroup implements SipListener { private int getErrorCode(Throwable exception) { String message = exception.getMessage(); if (exception instanceof UnknownHostException) { - return SipErrorCode.INVALID_REMOTE_URI; + return SipErrorCode.SERVER_UNREACHABLE; } else if (exception instanceof IOException) { return SipErrorCode.SOCKET_ERROR; } else { -- cgit v1.2.3 From c464603d67d5ec9d74c199fab3f18dd23a8169db Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Wed, 6 Oct 2010 16:46:59 +0800 Subject: Misc fixes for sim-eng build. Change-Id: I0c5dac1097abc924e66dab92d7d03d5051b4fd29 --- jni/rtp/AmrCodec.cpp | 2 +- jni/rtp/EchoSuppressor.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp index f3ecac2..72ee44e 100644 --- a/jni/rtp/AmrCodec.cpp +++ b/jni/rtp/AmrCodec.cpp @@ -73,7 +73,7 @@ int AmrCodec::set(int sampleRate, const char *fmtp) } // Handle mode-set and octet-align. - char *modes = strcasestr(fmtp, "mode-set="); + char *modes = (char*)strcasestr(fmtp, "mode-set="); if (modes) { mMode = 0; mModeSet = 0; diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index 92015a9..a1a7aed 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include -- cgit v1.2.3 From 919cd86bc78559f1e33af0fb66913cf0a94468f1 Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Wed, 6 Oct 2010 12:23:02 -0700 Subject: Fix simulator build, part 1/n Change-Id: If0a42ab262ee6aa6381ce95bd49baf232adb01c5 --- jni/rtp/AmrCodec.cpp | 2 +- jni/rtp/EchoSuppressor.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp index f3ecac2..84c7166 100644 --- a/jni/rtp/AmrCodec.cpp +++ b/jni/rtp/AmrCodec.cpp @@ -73,7 +73,7 @@ int AmrCodec::set(int sampleRate, const char *fmtp) } // Handle mode-set and octet-align. - char *modes = strcasestr(fmtp, "mode-set="); + const char *modes = strcasestr(fmtp, "mode-set="); if (modes) { mMode = 0; mModeSet = 0; diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index 92015a9..2ceebdc 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #define LOG_TAG "Echo" -- cgit v1.2.3 From fcc474636f42243cfce960158373b9c49636a556 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 6 Oct 2010 08:33:47 +0800 Subject: SIP: Fix busy authentication loop. Add a retry count and give up after two attempts. Also stop auto registration when server is unreachable. And rename onError() to restartLater() for better readability. http://b/issue?id=3066573 Change-Id: Icfa65c58546a1e2bf8e59e29584a3926c53c479b --- java/com/android/server/sip/SipService.java | 16 ++++---- java/com/android/server/sip/SipSessionGroup.java | 49 ++++++++++-------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index a7c61e5..d8a1572 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -825,11 +825,13 @@ public final class SipService extends ISipService.Stub { synchronized (SipService.this) { if (notCurrentSession(session)) return; - if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { - if (DEBUG) Log.d(TAG, " pause auto-registration"); - stop(); - } else { - onError(); + switch (errorCode) { + case SipErrorCode.INVALID_CREDENTIALS: + case SipErrorCode.SERVER_UNREACHABLE: + if (DEBUG) Log.d(TAG, " pause auto-registration"); + stop(); + default: + restartLater(); } mErrorCode = errorCode; @@ -846,11 +848,11 @@ public final class SipService extends ISipService.Stub { mErrorCode = SipErrorCode.TIME_OUT; mProxy.onRegistrationTimeout(session); - onError(); + restartLater(); } } - private void onError() { + private void restartLater() { mRegistered = false; restart(backoffDuration()); if (mKeepAliveProcess != null) { diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 37fffa8..57b3710 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -96,8 +96,6 @@ class SipSessionGroup implements SipListener { private SipStack mSipStack; private SipHelper mSipHelper; - private String mLastNonce; - private int mRPort; // session that processes INVITE requests private SipSessionImpl mCallReceiverSession; @@ -150,7 +148,6 @@ class SipSessionGroup implements SipListener { Log.d(TAG, " start stack for " + myself.getUriString()); stack.start(); - mLastNonce = null; mCallReceiverSession = null; mSessionMap.clear(); } @@ -366,8 +363,12 @@ class SipSessionGroup implements SipListener { ClientTransaction mClientTransaction; String mPeerSessionDescription; boolean mInCall; - boolean mReRegisterFlag = false; SessionTimer mTimer; + int mAuthenticationRetryCount; + + // for registration + boolean mReRegisterFlag = false; + int mRPort; // lightweight timer class SessionTimer { @@ -417,6 +418,8 @@ class SipSessionGroup implements SipListener { mState = SipSession.State.READY_TO_CALL; mInviteReceived = null; mPeerSessionDescription = null; + mRPort = 0; + mAuthenticationRetryCount = 0; if (mDialog != null) mDialog.delete(); mDialog = null; @@ -799,22 +802,10 @@ class SipSessionGroup implements SipListener { onRegistrationDone((state == SipSession.State.REGISTERING) ? getExpiryTime(((ResponseEvent) evt).getResponse()) : -1); - mLastNonce = null; - mRPort = 0; return true; case Response.UNAUTHORIZED: case Response.PROXY_AUTHENTICATION_REQUIRED: - if (!handleAuthentication(event)) { - if (mLastNonce == null) { - onRegistrationFailed(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - Log.v(TAG, "Incorrect username/password"); - onRegistrationFailed( - SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - } + handleAuthentication(event); return true; default: if (statusCode >= 500) { @@ -830,16 +821,24 @@ class SipSessionGroup implements SipListener { throws SipException { Response response = event.getResponse(); String nonce = getNonceFromResponse(response); - if (((nonce != null) && nonce.equals(mLastNonce)) || - (nonce == null)) { - mLastNonce = nonce; + if (nonce == null) { + onError(SipErrorCode.SERVER_ERROR, + "server does not provide challenge"); return false; - } else { + } else if (mAuthenticationRetryCount < 2) { mClientTransaction = mSipHelper.handleChallenge( event, getAccountManager()); mDialog = mClientTransaction.getDialog(); - mLastNonce = nonce; + mAuthenticationRetryCount++; + if (isLoggable(this, event)) { + Log.d(TAG, " authentication retry count=" + + mAuthenticationRetryCount); + } return true; + } else { + onError(SipErrorCode.INVALID_CREDENTIALS, + "incorrect username or password"); + return false; } } @@ -995,12 +994,6 @@ class SipSessionGroup implements SipListener { getRealmFromResponse(response)); } else if (handleAuthentication(event)) { addSipSession(this); - } else if (mLastNonce == null) { - onError(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - } else { - onError(SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); } return true; case Response.REQUEST_PENDING: -- cgit v1.2.3 From 22523a59d879cf47f1e9c202d001d569fad4f69e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 7 Oct 2010 09:14:57 +0800 Subject: Make SipService broadcast SIP_SERVICE_UP when it's up. http://b/issue?id=3062010 Change-Id: I13419fa3a8fdfba1977260f703e4dcaa42a6606c --- java/android/net/sip/SipManager.java | 7 +++++++ java/com/android/server/sip/SipService.java | 1 + 2 files changed, 8 insertions(+) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 80c35fb..8c32aa0 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -64,6 +64,13 @@ public class SipManager { /** Part of the incoming call intent. */ public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; + /** + * Action to broadcast when SipService is up. + * Internal use only. + * @hide + */ + public static final String ACTION_SIP_SERVICE_UP = + "android.net.sip.SIP_SERVICE_UP"; /** * Action string for the incoming call intent for the Phone app. * Internal use only. diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index d8a1572..1fa2400 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -100,6 +100,7 @@ public final class SipService extends ISipService.Stub { public static void start(Context context) { if (SipManager.isApiSupported(context)) { ServiceManager.addService("sip", new SipService(context)); + context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); Log.i(TAG, "SIP service started"); } } -- cgit v1.2.3 From 740d86eb428da640db411cf87addb4217de3d5d8 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Fri, 8 Oct 2010 06:06:56 +0800 Subject: Suppress harder for echo without affecting the volume of real voice. Change-Id: Ia3ce98eedd487a9e879ff0a4907b8c15b5707429 --- jni/rtp/EchoSuppressor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index a1a7aed..ad63cd6 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -157,11 +157,12 @@ void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) if (correlation > 0.3f) { float factor = 1.0f - correlation; factor *= factor; + factor /= 2.0; // suppress harder for (int i = 0; i < mSampleCount; ++i) { recorded[i] *= factor; } } -// LOGI("latency %5d, correlation %.10f", latency, correlation); + //LOGI("latency %5d, correlation %.10f", latency, correlation); // Increase RecordOffset. -- cgit v1.2.3 From 4e22f5d6687c18440e2f38c7e242d66ad834d1d7 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Sep 2010 04:11:32 +0800 Subject: Unhide SIP API. Change-Id: I09468e3149a242a3b1e085ad220eb74f84ac6c68 --- java/android/net/sip/SipAudioCall.java | 279 +++++++++++++--------- java/android/net/sip/SipErrorCode.java | 7 +- java/android/net/sip/SipException.java | 1 - java/android/net/sip/SipManager.java | 40 +++- java/android/net/sip/SipProfile.java | 1 - java/android/net/sip/SipRegistrationListener.java | 1 - java/android/net/sip/SipSession.java | 23 +- 7 files changed, 208 insertions(+), 144 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index c23da20..f55bade 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -43,9 +43,11 @@ import java.util.List; import java.util.Map; /** - * Class that handles an audio call over SIP. + * Class that handles an Internet audio call over SIP. {@link SipManager} + * facilitates instantiating a {@code SipAudioCall} object for making/receiving + * calls. See {@link SipManager#makeAudioCall} and + * {@link SipManager#takeAudioCall}. */ -/** @hide */ public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); private static final boolean RELEASE_SOCKET = true; @@ -56,7 +58,7 @@ public class SipAudioCall { public static class Listener { /** * Called when the call object is ready to make another call. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that is ready to make another call */ @@ -66,7 +68,7 @@ public class SipAudioCall { /** * Called when a request is sent out to initiate a new call. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -76,7 +78,7 @@ public class SipAudioCall { /** * Called when a new call comes in. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call * @param caller the SIP profile of the caller @@ -87,7 +89,7 @@ public class SipAudioCall { /** * Called when a RINGING response is received for the INVITE request - * sent. The default implementation calls {@link #onChange}. + * sent. The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -97,7 +99,7 @@ public class SipAudioCall { /** * Called when the session is established. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -107,7 +109,7 @@ public class SipAudioCall { /** * Called when the session is terminated. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -117,7 +119,7 @@ public class SipAudioCall { /** * Called when the peer is busy during session initialization. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -127,7 +129,7 @@ public class SipAudioCall { /** * Called when the call is on hold. - * The default implementation calls {@link #onChange}. + * The default implementation calls {@link #onChanged}. * * @param call the call object that carries out the audio call */ @@ -257,8 +259,10 @@ public class SipAudioCall { * * @return true if the call is established */ - public synchronized boolean isInCall() { - return mInCall; + public boolean isInCall() { + synchronized (this) { + return mInCall; + } } /** @@ -266,8 +270,10 @@ public class SipAudioCall { * * @return true if the call is on hold */ - public synchronized boolean isOnHold() { - return mHold; + public boolean isOnHold() { + synchronized (this) { + return mHold; + } } /** @@ -299,8 +305,10 @@ public class SipAudioCall { * * @return the local SIP profile */ - public synchronized SipProfile getLocalProfile() { - return mLocalProfile; + public SipProfile getLocalProfile() { + synchronized (this) { + return mLocalProfile; + } } /** @@ -308,8 +316,10 @@ public class SipAudioCall { * * @return the peer's SIP profile */ - public synchronized SipProfile getPeerProfile() { - return (mSipSession == null) ? null : mSipSession.getPeerProfile(); + public SipProfile getPeerProfile() { + synchronized (this) { + return (mSipSession == null) ? null : mSipSession.getPeerProfile(); + } } /** @@ -318,9 +328,11 @@ public class SipAudioCall { * * @return the session state */ - public synchronized int getState() { - if (mSipSession == null) return SipSession.State.READY_TO_CALL; - return mSipSession.getState(); + public int getState() { + synchronized (this) { + if (mSipSession == null) return SipSession.State.READY_TO_CALL; + return mSipSession.getState(); + } } @@ -330,8 +342,10 @@ public class SipAudioCall { * @return the session object that carries this call * @hide */ - public synchronized SipSession getSipSession() { - return mSipSession; + public SipSession getSipSession() { + synchronized (this) { + return mSipSession; + } } private SipSession.Listener createListener() { @@ -364,22 +378,25 @@ public class SipAudioCall { } @Override - public synchronized void onRinging(SipSession session, + public void onRinging(SipSession session, SipProfile peerProfile, String sessionDescription) { - if ((mSipSession == null) || !mInCall - || !session.getCallId().equals(mSipSession.getCallId())) { - // should not happen - session.endCall(); - return; - } + synchronized (SipAudioCall.this) { + 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(); + // session changing request + try { + String answer = createAnswer(sessionDescription).encode(); + mSipSession.answerCall(answer, SESSION_TIMEOUT); + } catch (Throwable e) { + Log.e(TAG, "onRinging()", e); + session.endCall(); + } } } @@ -508,18 +525,22 @@ public class SipAudioCall { * @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()); + public void attachCall(SipSession session, String sessionDescription) + throws SipException { + synchronized (this) { + 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); + if (getState() == SipSession.State.INCOMING_CALL) { + startRinging(); + } + } catch (Throwable e) { + Log.e(TAG, "attachCall()", e); + throwSipException(e); + } } } @@ -529,7 +550,7 @@ public class SipAudioCall { * and {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * - * @param callee the SIP profile to make the call to + * @param peerProfile the SIP profile to make the call to * @param sipSession the {@link SipSession} for carrying out the call * @param timeout the timeout value in seconds. Default value (defined by * SIP protocol) is used if {@code timeout} is zero or negative. @@ -537,15 +558,19 @@ public class SipAudioCall { * @throws SipException if the SIP service fails to create a session for the * call */ - public synchronized void makeCall(SipProfile peerProfile, - SipSession sipSession, int timeout) throws SipException { - mSipSession = sipSession; - try { - mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp())); - sipSession.setListener(createListener()); - sipSession.makeCall(peerProfile, createOffer().encode(), timeout); - } catch (IOException e) { - throw new SipException("makeCall()", e); + public void makeCall(SipProfile peerProfile, SipSession sipSession, + int timeout) throws SipException { + synchronized (this) { + mSipSession = sipSession; + try { + mAudioStream = new AudioStream(InetAddress.getByName( + getLocalIp())); + sipSession.setListener(createListener()); + sipSession.makeCall(peerProfile, createOffer().encode(), + timeout); + } catch (IOException e) { + throw new SipException("makeCall()", e); + } } } @@ -553,13 +578,15 @@ public class SipAudioCall { * 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; + public void endCall() throws SipException { + synchronized (this) { + stopRinging(); + stopCall(RELEASE_SOCKET); + mInCall = false; - // perform the above local ops first and then network op - if (mSipSession != null) mSipSession.endCall(); + // perform the above local ops first and then network op + if (mSipSession != null) mSipSession.endCall(); + } } /** @@ -574,13 +601,15 @@ public class SipAudioCall { * @see Listener.onError * @throws SipException if the SIP service fails to hold the call */ - public synchronized void holdCall(int timeout) throws SipException { + public void holdCall(int timeout) throws SipException { + synchronized (this) { if (mHold) return; - mSipSession.changeCall(createHoldOffer().encode(), timeout); - mHold = true; + mSipSession.changeCall(createHoldOffer().encode(), timeout); + mHold = true; - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } } /** @@ -594,13 +623,16 @@ public class SipAudioCall { * @see Listener.onError * @throws SipException if the SIP service fails to answer the call */ - 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); + public void answerCall(int timeout) throws SipException { + synchronized (this) { + stopRinging(); + try { + mAudioStream = new AudioStream(InetAddress.getByName( + getLocalIp())); + mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); + } catch (IOException e) { + throw new SipException("answerCall()", e); + } } } @@ -616,12 +648,14 @@ public class SipAudioCall { * @see Listener.onError * @throws SipException if the SIP service fails to unhold the call */ - 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); + public void continueCall(int timeout) throws SipException { + synchronized (this) { + if (!mHold) return; + mSipSession.changeCall(createContinueOffer().encode(), timeout); + mHold = false; + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); + } } private SimpleSessionDescription createOffer() { @@ -739,12 +773,15 @@ public class SipAudioCall { } /** Toggles mute. */ - public synchronized void toggleMute() { - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) { - audioGroup.setMode( - mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED); - mMuted = !mMuted; + public void toggleMute() { + synchronized (this) { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) { + audioGroup.setMode(mMuted + ? AudioGroup.MODE_NORMAL + : AudioGroup.MODE_MUTED); + mMuted = !mMuted; + } } } @@ -753,14 +790,18 @@ public class SipAudioCall { * * @return true if the call is muted */ - public synchronized boolean isMuted() { - return mMuted; + public boolean isMuted() { + synchronized (this) { + return mMuted; + } } /** Puts the device to speaker mode. */ - public synchronized void setSpeakerMode(boolean speakerMode) { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(speakerMode); + public void setSpeakerMode(boolean speakerMode) { + synchronized (this) { + ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .setSpeakerphoneOn(speakerMode); + } } /** @@ -785,14 +826,16 @@ public class SipAudioCall { * inputs. * @param result the result message to send when done */ - 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); + public void sendDtmf(int code, Message result) { + synchronized (this) { + 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(); } - if (result != null) result.sendToTarget(); } /** @@ -806,8 +849,10 @@ public class SipAudioCall { * yet been set up * @hide */ - public synchronized AudioStream getAudioStream() { - return mAudioStream; + public AudioStream getAudioStream() { + synchronized (this) { + return mAudioStream; + } } /** @@ -824,9 +869,11 @@ public class SipAudioCall { * @see #getAudioStream * @hide */ - public synchronized AudioGroup getAudioGroup() { - if (mAudioGroup != null) return mAudioGroup; - return ((mAudioStream == null) ? null : mAudioStream.getGroup()); + public AudioGroup getAudioGroup() { + synchronized (this) { + if (mAudioGroup != null) return mAudioGroup; + return ((mAudioStream == null) ? null : mAudioStream.getGroup()); + } } /** @@ -837,11 +884,13 @@ public class SipAudioCall { * @see #getAudioStream * @hide */ - public synchronized void setAudioGroup(AudioGroup group) { - if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { - mAudioStream.join(group); + public void setAudioGroup(AudioGroup group) { + synchronized (this) { + if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { + mAudioStream.join(group); + } + mAudioGroup = group; } - mAudioGroup = group; } /** @@ -981,8 +1030,10 @@ public class SipAudioCall { * * @param enabled true to enable; false to disable */ - public synchronized void setRingbackToneEnabled(boolean enabled) { - mRingbackToneEnabled = enabled; + public void setRingbackToneEnabled(boolean enabled) { + synchronized (this) { + mRingbackToneEnabled = enabled; + } } /** @@ -990,8 +1041,10 @@ public class SipAudioCall { * * @param enabled true to enable; false to disable */ - public synchronized void setRingtoneEnabled(boolean enabled) { - mRingtoneEnabled = enabled; + public void setRingtoneEnabled(boolean enabled) { + synchronized (this) { + mRingtoneEnabled = enabled; + } } private void startRingbackTone() { diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index a55ab25..6aee5f1 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -19,10 +19,9 @@ package android.net.sip; /** * Defines error code returned in * {@link SipRegistrationListener#onRegistrationFailed}, - * {@link ISipSessionListener#onError}, - * {@link ISipSessionListener#onCallChangeFailed} and - * {@link ISipSessionListener#onRegistrationFailed}. - * @hide + * {@link SipSession.Listener#onError}, + * {@link SipSession.Listener#onCallChangeFailed} and + * {@link SipSession.Listener#onRegistrationFailed}. */ public class SipErrorCode { /** Not an error. */ diff --git a/java/android/net/sip/SipException.java b/java/android/net/sip/SipException.java index f0d846b..225b94f 100644 --- a/java/android/net/sip/SipException.java +++ b/java/android/net/sip/SipException.java @@ -18,7 +18,6 @@ package android.net.sip; /** * General SIP-related exception class. - * @hide */ public class SipException extends Exception { public SipException() { diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 8c32aa0..ee0e3cd 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -48,7 +48,8 @@ import java.text.ParseException; *
  • process SIP events directly with a {@link SipSession} created by * {@link #createSipSession}.
  • * - * @hide + * {@code SipManager} can only be instantiated if SIP API is supported by the + * device. (See {@link #isApiSupported}). */ public class SipManager { /** @@ -58,10 +59,17 @@ public class SipManager { */ public static final int INCOMING_CALL_RESULT_CODE = 101; - /** Part of the incoming call intent. */ + /** + * Key to retrieve the call ID from an incoming call intent. + * @see #open(SipProfile, PendingIntent, SipRegistrationListener) + */ public static final String EXTRA_CALL_ID = "android:sipCallID"; - /** Part of the incoming call intent. */ + /** + * Key to retrieve the offered session description from an incoming call + * intent. + * @see #open(SipProfile, PendingIntent, SipRegistrationListener) + */ public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; /** @@ -178,7 +186,11 @@ public class SipManager { * 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. + * in order to receive calls from the provider. When the SIP service + * receives a new call, it will send out an intent with the provided action + * string. The intent contains a call ID extra and an offer session + * description string extra. Use {@link #getCallId} and + * {@link #getOfferSessionDescription} to retrieve those extras. * * @param localProfile the SIP profile to receive incoming calls for * @param incomingCallPendingIntent When an incoming call is received, the @@ -194,6 +206,9 @@ public class SipManager { * @throws NullPointerException if {@code incomingCallPendingIntent} is null * @throws SipException if the profile contains incorrect settings or * calling the SIP service results in an error + * @see #isIncomingCallIntent + * @see #getCallId + * @see #getOfferSessionDescription */ public void open(SipProfile localProfile, PendingIntent incomingCallPendingIntent, @@ -291,7 +306,8 @@ public class SipManager { * @param peerProfile the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; * can be null - * @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. * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error * @see SipAudioCall.Listener.onError @@ -321,7 +337,8 @@ public class SipManager { * @param peerProfileUri URI of the SIP profile to make the call to * @param listener to listen to the call events from {@link SipAudioCall}; * can be null - * @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. * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error * @see SipAudioCall.Listener.onError @@ -489,7 +506,7 @@ public class SipManager { } /** - * Gets the {@link ISipSession} that handles the incoming call. For audio + * Gets the {@link SipSession} that handles the incoming call. For audio * calls, consider to use {@link SipAudioCall} to handle the incoming call. * See {@link #takeAudioCall}. Note that the method may be called only once * for the same intent. For subsequent calls on the same intent, the method @@ -498,11 +515,12 @@ public class SipManager { * @param incomingCallIntent the incoming call broadcast intent * @return the session object that handles the incoming call */ - public ISipSession getSessionFor(Intent incomingCallIntent) + public SipSession getSessionFor(Intent incomingCallIntent) throws SipException { try { String callId = getCallId(incomingCallIntent); - return mSipService.getPendingSession(callId); + ISipSession s = mSipService.getPendingSession(callId); + return new SipSession(s); } catch (RemoteException e) { throw new SipException("getSessionFor()", e); } @@ -514,8 +532,8 @@ public class SipManager { } /** - * Creates a {@link ISipSession} with the specified profile. Use other - * methods, if applicable, instead of interacting with {@link ISipSession} + * Creates a {@link SipSession} with the specified profile. Use other + * methods, if applicable, instead of interacting with {@link SipSession} * directly. * * @param localProfile the SIP profile the session is associated with diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 6d5cb3c..dddb07d 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -33,7 +33,6 @@ import javax.sip.address.URI; /** * Class containing a SIP account, domain and server information. - * @hide */ public class SipProfile implements Parcelable, Serializable, Cloneable { private static final long serialVersionUID = 1L; diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index 37c9ce2..e1f35ad 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -18,7 +18,6 @@ package android.net.sip; /** * Listener class to listen to SIP registration events. - * @hide */ public interface SipRegistrationListener { /** diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java index 0cc7206..9c08e46 100644 --- a/java/android/net/sip/SipSession.java +++ b/java/android/net/sip/SipSession.java @@ -22,14 +22,12 @@ 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. */ @@ -101,7 +99,6 @@ public final class SipSession { /** * Listener class that listens to {@link SipSession} events. - * @hide */ public static class Listener { /** @@ -281,7 +278,7 @@ public final class SipSession { /** * Gets the session state. The value returned must be one of the states in - * {@link SipSessionState}. + * {@link State}. * * @return the session state */ @@ -339,7 +336,7 @@ public final class SipSession { * 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}. + * in {@link State#READY_TO_CALL}. * * @param duration duration in second before the registration expires * @see Listener @@ -357,7 +354,7 @@ public final class SipSession { * 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}. + * session state is in {@link State#READY_TO_CALL}. * * @see Listener */ @@ -372,7 +369,7 @@ public final class SipSession { /** * 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}. + * the session state is in {@link State#READY_TO_CALL}. * * @param callee the SIP profile to make the call to * @param sessionDescription the session description of this call @@ -393,7 +390,7 @@ public final class SipSession { /** * 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}. + * {@link State#INCOMING_CALL}. * * @param sessionDescription the session description to answer this call * @param timeout the session will be timed out if the call is not @@ -411,10 +408,10 @@ public final class SipSession { /** * 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}. + * in {@link State#IN_CALL}, + * {@link State#INCOMING_CALL}, + * {@link State#OUTGOING_CALL} or + * {@link State#OUTGOING_CALL_RING_BACK}. */ public void endCall() { try { @@ -426,7 +423,7 @@ public final class SipSession { /** * Changes the session description during a call. The method is only valid - * to call when the session state is in {@link SipSessionState#IN_CALL}. + * to call when the session state is in {@link State#IN_CALL}. * * @param sessionDescription the new session description * @param timeout the session will be timed out if the call is not -- cgit v1.2.3 From 8127b35b75c13f853dc8eb2fed8bba4bf99e3eed Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 8 Oct 2010 08:59:38 +0800 Subject: SipService: add permission check for using API Change-Id: Ifd85ba07f1b913011cb3e80e5027c67bfe3db280 --- java/com/android/server/sip/SipService.java | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 1fa2400..db1931b 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -123,6 +123,8 @@ public final class SipService extends ISipService.Stub { } public synchronized SipProfile[] getListOfProfiles() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); boolean isCallerRadio = isCallerRadio(); ArrayList profiles = new ArrayList(); for (SipSessionGroupExt group : mSipGroups.values()) { @@ -134,6 +136,8 @@ public final class SipService extends ISipService.Stub { } public void open(SipProfile localProfile) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); try { createGroup(localProfile); @@ -146,6 +150,8 @@ public final class SipService extends ISipService.Stub { public synchronized void open3(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); if (incomingCallPendingIntent == null) { Log.w(TAG, "incomingCallPendingIntent cannot be null; " @@ -159,7 +165,7 @@ public final class SipService extends ISipService.Stub { incomingCallPendingIntent, listener); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); - if (isWifiOn()) grabWifiLock(); + if (isWifiActive()) grabWifiLock(); } } catch (SipException e) { Log.e(TAG, "openToReceiveCalls()", e); @@ -181,6 +187,8 @@ public final class SipService extends ISipService.Stub { } public synchronized void close(String localProfileUri) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return; if (!isCallerCreatorOrRadio(group)) { @@ -191,10 +199,12 @@ public final class SipService extends ISipService.Stub { group = mSipGroups.remove(localProfileUri); notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (isWifiOn() && !anyOpened()) releaseWifiLock(); + if (isWifiActive() && !anyOpened()) releaseWifiLock(); } public synchronized boolean isOpened(String localProfileUri) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return false; if (isCallerCreatorOrRadio(group)) { @@ -206,6 +216,8 @@ public final class SipService extends ISipService.Stub { } public synchronized boolean isRegistered(String localProfileUri) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return false; if (isCallerCreatorOrRadio(group)) { @@ -218,6 +230,8 @@ public final class SipService extends ISipService.Stub { public synchronized void setRegistrationListener(String localProfileUri, ISipSessionListener listener) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return; if (isCallerCreator(group)) { @@ -229,6 +243,8 @@ public final class SipService extends ISipService.Stub { public synchronized ISipSession createSession(SipProfile localProfile, ISipSessionListener listener) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); if (!mConnected) return null; try { @@ -241,6 +257,8 @@ public final class SipService extends ISipService.Stub { } public synchronized ISipSession getPendingSession(String callId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.USE_SIP, null); if (callId == null) return null; return mPendingSessions.get(callId); } @@ -330,9 +348,8 @@ public final class SipService extends ISipService.Stub { } } - private boolean isWifiOn() { + private boolean isWifiActive() { return "WIFI".equalsIgnoreCase(mNetworkType); - //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType)); } private synchronized void onConnectivityChanged( -- cgit v1.2.3 From f452fd81f1a308428b0bfbae883fddb9caced878 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Sat, 9 Oct 2010 08:37:40 +0800 Subject: Do not release the wifi lock if the screen is off. We need to be able to receive calls if the device is able to reassociate with any AP later on. Change-Id: Ib7aafb98386bf250ed9b5ec0a5b519594efa1649 --- java/com/android/server/sip/SipService.java | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index db1931b..ee554b5 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -92,6 +92,7 @@ public final class SipService extends ISipService.Stub { new HashMap(); private ConnectivityReceiver mConnectivityReceiver; + private boolean mScreenOn; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -111,11 +112,27 @@ public final class SipService extends ISipService.Stub { mConnectivityReceiver = new ConnectivityReceiver(); context.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + context.registerReceiver(mScreenOnOffReceiver, + new IntentFilter(Intent.ACTION_SCREEN_ON)); + context.registerReceiver(mScreenOnOffReceiver, + new IntentFilter(Intent.ACTION_SCREEN_OFF)); mTimer = new WakeupTimer(context); mWifiOnly = SipManager.isSipWifiOnly(context); } + BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mScreenOn = true; + } else if (Intent.ACTION_SCREEN_ON.equals(action)) { + mScreenOn = false; + } + } + }; + private MyExecutor getExecutor() { // create mExecutor lazily if (mExecutor == null) mExecutor = new MyExecutor(); @@ -366,7 +383,10 @@ public final class SipService extends ISipService.Stub { boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); boolean wifiOn = isWifi && connected; if (wifiOff) { - releaseWifiLock(); + if (mScreenOn) releaseWifiLock(); + // If the screen is off, we still keep the wifi lock in order + // to be able to reassociate with any available AP. Otherwise, + // the wifi driver could be stopped after 15 mins of idle time. } else if (wifiOn) { if (anyOpened()) grabWifiLock(); } -- cgit v1.2.3 From e9fe79e34d2c4100cd391bcf21df8bd50d5f3228 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Sun, 10 Oct 2010 23:48:10 +0800 Subject: SipHelper: add debug log for challenge responses. Change-Id: If0143a0f076ef30b1b8998e477df933923bfa7b1 --- java/com/android/server/sip/SipHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 050eddc..2514262 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -238,6 +238,8 @@ class SipHelper { ClientTransaction tid = responseEvent.getClientTransaction(); ClientTransaction ct = authenticationHelper.handleChallenge( responseEvent.getResponse(), tid, mSipProvider, 5); + if (DEBUG) Log.d(TAG, "send request with challenge response: " + + ct.getRequest()); ct.sendRequest(); return ct; } -- cgit v1.2.3 From 0b88a073712b1510db7c636f89c4e19f0131449a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 12 Oct 2010 10:32:29 +0800 Subject: SipService: fix a missing switch-case break. Change-Id: I638eecd8000293d4cb37b3595c02ca33df4924eb --- java/com/android/server/sip/SipService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index ee554b5..6f426c9 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -868,6 +868,7 @@ public final class SipService extends ISipService.Stub { case SipErrorCode.SERVER_UNREACHABLE: if (DEBUG) Log.d(TAG, " pause auto-registration"); stop(); + break; default: restartLater(); } -- cgit v1.2.3 From 27820a94e73622f647daf8678180abc541d3a092 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 12 Oct 2010 15:40:24 +0800 Subject: Fix SipSessionGroup from throwing ConcurrentModificationException http://b/issue?id=3087256 Change-Id: I67df64105db7c1295649f1f3ce77f99025ce3d44 --- java/com/android/server/sip/SipSessionGroup.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 57b3710..b5f8d39 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -153,7 +153,13 @@ class SipSessionGroup implements SipListener { } synchronized void onConnectivityChanged() { - for (SipSessionImpl s : mSessionMap.values()) { + SipSessionImpl[] ss = mSessionMap.values().toArray( + new SipSessionImpl[mSessionMap.size()]); + // Iterate on the copied array instead of directly on mSessionMap to + // avoid ConcurrentModificationException being thrown when + // SipSessionImpl removes itself from mSessionMap in onError() in the + // following loop. + for (SipSessionImpl s : ss) { s.onError(SipErrorCode.DATA_CONNECTION_LOST, "data connection lost"); } -- cgit v1.2.3 From 315d4a3453601980716da96b444fff60191fc6a2 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 13 Oct 2010 09:43:35 +0800 Subject: SipService: mScreenOn is flipped to wrong value. http://b/issue?id=3077454 Change-Id: I23b6f70730074689b939e449c2c202ce8ffb586f --- java/com/android/server/sip/SipService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 6f426c9..42b4e7c 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -126,9 +126,9 @@ public final class SipService extends ISipService.Stub { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mScreenOn = true; - } else if (Intent.ACTION_SCREEN_ON.equals(action)) { mScreenOn = false; + } else if (Intent.ACTION_SCREEN_ON.equals(action)) { + mScreenOn = true; } } }; -- cgit v1.2.3 From 9abf73f1bde7b1617603c888650dbda9d6a7fddf Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 13 Oct 2010 17:05:59 +0800 Subject: Make SipService listen to WIFI state change events. + Grab a WIFI lock if any account is opened to receive calls and WIFI is enabled + Release the WIFI lock if no account is opened to receive calls or WIFI is disabled + Remove screen on/off event receiver http://b/issue?id=3077454 Change-Id: Ifdf60a850bcf4106c75ec1e7563b26d8b33d7e92 --- java/com/android/server/sip/SipService.java | 50 +++++++++++++---------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 42b4e7c..1a17d38 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -92,7 +92,7 @@ public final class SipService extends ISipService.Stub { new HashMap(); private ConnectivityReceiver mConnectivityReceiver; - private boolean mScreenOn; + private boolean mWifiEnabled; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -112,23 +112,32 @@ public final class SipService extends ISipService.Stub { mConnectivityReceiver = new ConnectivityReceiver(); context.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - context.registerReceiver(mScreenOnOffReceiver, - new IntentFilter(Intent.ACTION_SCREEN_ON)); - context.registerReceiver(mScreenOnOffReceiver, - new IntentFilter(Intent.ACTION_SCREEN_OFF)); + context.registerReceiver(mWifiStateReceiver, + new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); mTimer = new WakeupTimer(context); mWifiOnly = SipManager.isSipWifiOnly(context); } - BroadcastReceiver mScreenOnOffReceiver = new BroadcastReceiver() { + BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mScreenOn = false; - } else if (Intent.ACTION_SCREEN_ON.equals(action)) { - mScreenOn = true; + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN); + synchronized (SipService.this) { + switch (state) { + case WifiManager.WIFI_STATE_ENABLED: + mWifiEnabled = true; + if (anyOpened()) grabWifiLock(); + break; + case WifiManager.WIFI_STATE_DISABLED: + mWifiEnabled = false; + releaseWifiLock(); + break; + } + } } } }; @@ -182,7 +191,7 @@ public final class SipService extends ISipService.Stub { incomingCallPendingIntent, listener); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); - if (isWifiActive()) grabWifiLock(); + if (mWifiEnabled) grabWifiLock(); } } catch (SipException e) { Log.e(TAG, "openToReceiveCalls()", e); @@ -216,7 +225,7 @@ public final class SipService extends ISipService.Stub { group = mSipGroups.remove(localProfileUri); notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (isWifiActive() && !anyOpened()) releaseWifiLock(); + if (!anyOpened()) releaseWifiLock(); } public synchronized boolean isOpened(String localProfileUri) { @@ -349,7 +358,7 @@ public final class SipService extends ISipService.Stub { private void grabWifiLock() { if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "acquire wifi lock"); + if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock"); mWifiLock = ((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); @@ -359,16 +368,12 @@ public final class SipService extends ISipService.Stub { private void releaseWifiLock() { if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "release wifi lock"); + if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock"); mWifiLock.release(); mWifiLock = null; } } - private boolean isWifiActive() { - return "WIFI".equalsIgnoreCase(mNetworkType); - } - private synchronized void onConnectivityChanged( String type, boolean connected) { if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " @@ -382,14 +387,6 @@ public final class SipService extends ISipService.Stub { boolean isWifi = "WIFI".equalsIgnoreCase(type); boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); boolean wifiOn = isWifi && connected; - if (wifiOff) { - if (mScreenOn) releaseWifiLock(); - // If the screen is off, we still keep the wifi lock in order - // to be able to reassociate with any available AP. Otherwise, - // the wifi driver could be stopped after 15 mins of idle time. - } else if (wifiOn) { - if (anyOpened()) grabWifiLock(); - } try { boolean wasConnected = mConnected; @@ -409,7 +406,6 @@ public final class SipService extends ISipService.Stub { group.onConnectivityChanged(true); } } - } catch (SipException e) { Log.e(TAG, "onConnectivityChanged()", e); } -- cgit v1.2.3 From 257c7e2b4193bff3793fcedd8b34b7fec2b1019b Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 13 Oct 2010 23:32:17 +0800 Subject: SipService: add wake lock for multiple components. + Add MyWakeLock to maintain a global wake lock for multiple components. + Use a Set to store components that want to hold the lock. + When the first component enters the set, we grab the global wake lock. + When the set becomes empty, we release the global lock. + In places like no account being opened to receive calls, we reset the wake lock just to be safe from possible leakage. + Make MyExecutor aware of the wake lock. It will grab the wake lock on behalf of the task so that tasks don't need to worry about the lock. + Connectivity receiver is modified to be executed in MyExecutor. + WakeupTimer handler is already protected by AlarmManager's wake lock but all the timeout handlers that register themselves to the WakeupTimer are to be executed in MyExecutor to be protected by the wake lock. + Remove unnecessary code in the Keepalive and registration processes. Since both processes are executed in MyExecutor submitted by the WakeupTimer (as they are timeout handlers registered to the WakeupTimer), they don't need to add themselves to MyExecutor explicitly in their run() callbacks. + Make the keepalive process wait for at most 3 seconds instead of forever for server response. It could cause the wake lock to be held longer than necessary and is a potential cause for ANR. http://b/issue?id=3081828 Related bug: http://b/issue?id=3087153 Change-Id: Idee0ddb837e67daa0d5092c012bb242bd7c18431 --- java/com/android/server/sip/SipService.java | 190 +++++++++++++++-------- java/com/android/server/sip/SipSessionGroup.java | 10 +- 2 files changed, 132 insertions(+), 68 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 1a17d38..f41f156 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -39,6 +39,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -54,6 +55,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Timer; @@ -93,6 +95,7 @@ public final class SipService extends ISipService.Stub { private ConnectivityReceiver mConnectivityReceiver; private boolean mWifiEnabled; + private MyWakeLock mMyWakeLock; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -114,6 +117,8 @@ public final class SipService extends ISipService.Stub { new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); context.registerReceiver(mWifiStateReceiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + mMyWakeLock = new MyWakeLock((PowerManager) + context.getSystemService(Context.POWER_SERVICE)); mTimer = new WakeupTimer(context); mWifiOnly = SipManager.isSipWifiOnly(context); @@ -225,7 +230,11 @@ public final class SipService extends ISipService.Stub { group = mSipGroups.remove(localProfileUri); notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (!anyOpened()) releaseWifiLock(); + + if (!anyOpened()) { + releaseWifiLock(); + mMyWakeLock.reset(); // in case there's leak + } } public synchronized boolean isOpened(String localProfileUri) { @@ -405,6 +414,8 @@ public final class SipService extends ISipService.Stub { for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } + } else { + mMyWakeLock.reset(); // in case there's a leak } } catch (SipException e) { Log.e(TAG, "onConnectivityChanged()", e); @@ -581,7 +592,7 @@ public final class SipService extends ISipService.Stub { } // KeepAliveProcess is controlled by AutoRegistrationProcess. - // All methods will be invoked in sync with SipService.this except realRun() + // All methods will be invoked in sync with SipService.this. private class KeepAliveProcess implements Runnable { private static final String TAG = "\\KEEPALIVE/"; private static final int INTERVAL = 10; @@ -600,43 +611,33 @@ public final class SipService extends ISipService.Stub { // timeout handler public void run() { - if (!mRunning) return; - final SipSessionGroup.SipSessionImpl session = mSession; - - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(session); - } - }); - } - - // real timeout handler - private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - if (notCurrentSession(session)) return; + if (!mRunning) return; - session = session.duplicate(); - if (DEBUGV) Log.v(TAG, "~~~ keepalive"); - mTimer.cancel(this); - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - mSession.register(EXPIRY_TIME); - } else { - mTimer.set(INTERVAL * 1000, this); + if (DEBUGV) Log.v(TAG, "~~~ keepalive: " + + mSession.getLocalProfile().getUriString()); + SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + try { + session.sendKeepAlive(); + if (session.isReRegisterRequired()) { + // Acquire wake lock for the registration process. The + // lock will be released when registration is complete. + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); + } + } catch (Throwable t) { + Log.w(TAG, "keepalive error: " + t); } } } public void stop() { + if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:" + + mSession.getLocalProfile().getUriString()); mRunning = false; mSession = null; mTimer.cancel(this); } - - private boolean notCurrentSession(ISipSession session) { - return (session != mSession) || !mRunning; - } } private class AutoRegistrationProcess extends SipSessionAdapter @@ -667,6 +668,7 @@ public final class SipService extends ISipService.Stub { // start unregistration to clear up old registration at server // TODO: when rfc5626 is deployed, use reg-id and sip.instance // in registration to avoid adding duplicate entries to server + mMyWakeLock.acquire(mSession); mSession.unregister(); if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " + mSession.getLocalProfile().getUriString()); @@ -676,8 +678,11 @@ public final class SipService extends ISipService.Stub { public void stop() { if (!mRunning) return; mRunning = false; - mSession.setListener(null); - if (mConnected && mRegistered) mSession.unregister(); + mMyWakeLock.release(mSession); + if (mSession != null) { + mSession.setListener(null); + if (mConnected && mRegistered) mSession.unregister(); + } mTimer.cancel(this); if (mKeepAliveProcess != null) { @@ -734,29 +739,18 @@ public final class SipService extends ISipService.Stub { return mRegistered; } - // timeout handler + // timeout handler: re-register public void run() { synchronized (SipService.this) { if (!mRunning) return; - final SipSessionGroup.SipSessionImpl session = mSession; - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(session); - } - }); - } - } - - // real timeout handler - private void realRun(SipSessionGroup.SipSessionImpl session) { - synchronized (SipService.this) { - if (notCurrentSession(session)) return; mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; if (DEBUG) Log.d(TAG, "~~~ registering"); - if (mConnected) session.register(EXPIRY_TIME); + if (mConnected) { + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); + } } } @@ -806,6 +800,7 @@ public final class SipService extends ISipService.Stub { private boolean notCurrentSession(ISipSession session) { if (session != mSession) { ((SipSessionGroup.SipSessionImpl) session).setListener(null); + mMyWakeLock.release(session); return true; } return !mRunning; @@ -842,6 +837,7 @@ public final class SipService extends ISipService.Stub { mKeepAliveProcess.start(); } } + mMyWakeLock.release(session); } else { mRegistered = false; mExpiryTime = -1L; @@ -872,6 +868,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = errorCode; mErrorMessage = message; mProxy.onRegistrationFailed(session, errorCode, message); + mMyWakeLock.release(session); } } @@ -884,6 +881,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = SipErrorCode.TIME_OUT; mProxy.onRegistrationTimeout(session); restartLater(); + mMyWakeLock.release(session); } } @@ -902,7 +900,16 @@ public final class SipService extends ISipService.Stub { private MyTimerTask mTask; @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(final Context context, final Intent intent) { + // Run the handler in MyExecutor to be protected by wake lock + getExecutor().execute(new Runnable() { + public void run() { + onReceiveInternal(context, intent); + } + }); + } + + private void onReceiveInternal(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { Bundle b = intent.getExtras(); @@ -970,11 +977,13 @@ public final class SipService extends ISipService.Stub { if (mTask != null) mTask.cancel(); mTask = new MyTimerTask(type, connected); mTimer.schedule(mTask, 2 * 1000L); - // TODO: hold wakup lock so that we can finish change before - // the device goes to sleep + // hold wakup lock so that we can finish changes before the + // device goes to sleep + mMyWakeLock.acquire(mTask); } else { if ((mTask != null) && mTask.mNetworkType.equals(type)) { mTask.cancel(); + mMyWakeLock.release(mTask); } onConnectivityChanged(type, false); } @@ -994,7 +1003,7 @@ public final class SipService extends ISipService.Stub { @Override public void run() { // delegate to mExecutor - getExecutor().addTask(new Runnable() { + getExecutor().execute(new Runnable() { public void run() { realRun(); } @@ -1012,6 +1021,7 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType + (mConnected ? " CONNECTED" : "DISCONNECTED")); onConnectivityChanged(mNetworkType, mConnected); + mMyWakeLock.release(this); } } } @@ -1019,7 +1029,6 @@ public final class SipService extends ISipService.Stub { // TODO: clean up pending SipSession(s) periodically - /** * Timer that can schedule events to occur even when the device is in sleep. * Only used internally in this package. @@ -1209,7 +1218,8 @@ public final class SipService extends ISipService.Stub { } @Override - public synchronized void onReceive(Context context, Intent intent) { + public void onReceive(Context context, Intent intent) { + // This callback is already protected by AlarmManager's wake lock. String action = intent.getAction(); if (getAction().equals(action) && intent.getExtras().containsKey(TRIGGER_TIME)) { @@ -1236,7 +1246,7 @@ public final class SipService extends ISipService.Stub { } } - private void execute(long triggerTime) { + private synchronized void execute(long triggerTime) { if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; @@ -1248,9 +1258,8 @@ public final class SipService extends ISipService.Stub { event.mLastTriggerTime = event.mTriggerTime; event.mTriggerTime += event.mPeriod; - // run the callback in a new thread to prevent deadlock - new Thread(event.mCallback, "SipServiceTimerCallbackThread") - .start(); + // run the callback in the handler thread to prevent deadlock + getExecutor().execute(event.mCallback); } if (DEBUG_TIMER) { Log.d(TAG, "after timeout execution"); @@ -1314,29 +1323,78 @@ public final class SipService extends ISipService.Stub { } } - // Single-threaded executor - private static class MyExecutor extends Handler { + private static Looper createLooper() { + HandlerThread thread = new HandlerThread("SipService.Executor"); + thread.start(); + return thread.getLooper(); + } + + // Executes immediate tasks in a single thread. + // Hold/release wake lock for running tasks + private class MyExecutor extends Handler { MyExecutor() { super(createLooper()); } - private static Looper createLooper() { - HandlerThread thread = new HandlerThread("SipService"); - thread.start(); - return thread.getLooper(); - } - - void addTask(Runnable task) { + void execute(Runnable task) { + mMyWakeLock.acquire(task); Message.obtain(this, 0/* don't care */, task).sendToTarget(); } @Override public void handleMessage(Message msg) { if (msg.obj instanceof Runnable) { - ((Runnable) msg.obj).run(); + executeInternal((Runnable) msg.obj); } else { Log.w(TAG, "can't handle msg: " + msg); } } + + private void executeInternal(Runnable task) { + try { + task.run(); + } catch (Throwable t) { + Log.e(TAG, "run task: " + task, t); + } finally { + mMyWakeLock.release(task); + } + } + } + + private static class MyWakeLock { + private PowerManager mPowerManager; + private PowerManager.WakeLock mWakeLock; + private HashSet mHolders = new HashSet(); + + MyWakeLock(PowerManager powerManager) { + mPowerManager = powerManager; + } + + synchronized void reset() { + mHolders.clear(); + release(null); + if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock"); + } + + synchronized void acquire(Object holder) { + mHolders.add(holder); + if (mWakeLock == null) { + mWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); + } + if (!mWakeLock.isHeld()) mWakeLock.acquire(); + if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count=" + + mHolders.size()); + } + + synchronized void release(Object holder) { + mHolders.remove(holder); + if ((mWakeLock != null) && mHolders.isEmpty() + && mWakeLock.isHeld()) { + mWakeLock.release(); + } + if (DEBUGV) Log.v(TAG, "release wakelock: holder count=" + + mHolders.size()); + } } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index b5f8d39..2b8058f 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -547,8 +547,14 @@ class SipSessionGroup implements SipListener { mState = SipSession.State.PINGING; try { processCommand(new OptionsCommand()); - while (SipSession.State.PINGING == mState) { - Thread.sleep(1000); + for (int i = 0; i < 15; i++) { + if (SipSession.State.PINGING != mState) break; + Thread.sleep(200); + } + if (SipSession.State.PINGING == mState) { + // FIXME: what to do if server doesn't respond + reset(); + if (DEBUG) Log.w(TAG, "no response from ping"); } } catch (SipException e) { Log.e(TAG, "sendKeepAlive failed", e); -- cgit v1.2.3 From acedadc1967457ac2f8981c67f884fd8c0ee853c Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 15 Oct 2010 01:22:44 +0800 Subject: SipService: add wake lock for incoming INVITE packets. + Keep the wake lock for 500ms. (Some measurements on N1 indicate 160~180ms needed to bring up InCallScreen but since INVITE doesn't come in frequently we can be more generous just to be safe.) + Move MyWakeupLock out of SipService so SipSessionGroup can use it without awkward inter-dependency with SipService. + Add acquire(int timeout) to be used to create the "timed" wake lock. http://b/issue?id=3081828 Change-Id: Iffd1d78d1a5cae9f795252ada75310917095204d --- java/com/android/server/sip/SipService.java | 50 +++-------------- java/com/android/server/sip/SipSessionGroup.java | 17 +++++- java/com/android/server/sip/SipWakeLock.java | 71 ++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 java/com/android/server/sip/SipWakeLock.java diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index f41f156..1df08c0 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -55,7 +55,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Timer; @@ -67,8 +66,8 @@ import javax.sip.SipException; * @hide */ public final class SipService extends ISipService.Stub { - private static final String TAG = "SipService"; - private static final boolean DEBUGV = false; + static final String TAG = "SipService"; + static final boolean DEBUGV = false; private static final boolean DEBUG = true; private static final boolean DEBUG_TIMER = DEBUG && false; private static final int EXPIRY_TIME = 3600; @@ -95,7 +94,7 @@ public final class SipService extends ISipService.Stub { private ConnectivityReceiver mConnectivityReceiver; private boolean mWifiEnabled; - private MyWakeLock mMyWakeLock; + private SipWakeLock mMyWakeLock; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -117,7 +116,7 @@ public final class SipService extends ISipService.Stub { new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); context.registerReceiver(mWifiStateReceiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); - mMyWakeLock = new MyWakeLock((PowerManager) + mMyWakeLock = new SipWakeLock((PowerManager) context.getSystemService(Context.POWER_SERVICE)); mTimer = new WakeupTimer(context); @@ -459,7 +458,8 @@ public final class SipService extends ISipService.Stub { private SipSessionGroup createSipSessionGroup(String localIp, SipProfile localProfile, String password) throws SipException { try { - return new SipSessionGroup(localIp, localProfile, password); + return new SipSessionGroup(localIp, localProfile, password, + mMyWakeLock); } catch (IOException e) { // network disconnected Log.w(TAG, "createSipSessionGroup(): network disconnected?"); @@ -546,6 +546,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRinging(ISipSession s, SipProfile caller, String sessionDescription) { + if (DEBUGV) Log.d(TAG, "<<<<< onRinging()"); SipSessionGroup.SipSessionImpl session = (SipSessionGroup.SipSessionImpl) s; synchronized (SipService.this) { @@ -1360,41 +1361,4 @@ public final class SipService extends ISipService.Stub { } } } - - private static class MyWakeLock { - private PowerManager mPowerManager; - private PowerManager.WakeLock mWakeLock; - private HashSet mHolders = new HashSet(); - - MyWakeLock(PowerManager powerManager) { - mPowerManager = powerManager; - } - - synchronized void reset() { - mHolders.clear(); - release(null); - if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock"); - } - - synchronized void acquire(Object holder) { - mHolders.add(holder); - if (mWakeLock == null) { - mWakeLock = mPowerManager.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); - } - if (!mWakeLock.isHeld()) mWakeLock.acquire(); - if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count=" - + mHolders.size()); - } - - synchronized void release(Object holder) { - mHolders.remove(holder); - if ((mWakeLock != null) && mHolders.isEmpty() - && mWakeLock.isHeld()) { - mWakeLock.release(); - } - if (DEBUGV) Log.v(TAG, "release wakelock: holder count=" - + mHolders.size()); - } - } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 2b8058f..d861fa5 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -84,6 +84,7 @@ class SipSessionGroup implements SipListener { private static final String ANONYMOUS = "anonymous"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds + private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds private static final EventObject DEREGISTER = new EventObject("Deregister"); private static final EventObject END_CALL = new EventObject("End call"); @@ -101,6 +102,8 @@ class SipSessionGroup implements SipListener { private SipSessionImpl mCallReceiverSession; private String mLocalIp; + private SipWakeLock mWakeLock; + // call-id-to-SipSession map private Map mSessionMap = new HashMap(); @@ -110,10 +113,11 @@ class SipSessionGroup implements SipListener { * @param password the password of the profile * @throws IOException if cannot assign requested address */ - public SipSessionGroup(String localIp, SipProfile myself, String password) - throws SipException, IOException { + public SipSessionGroup(String localIp, SipProfile myself, String password, + SipWakeLock wakeLock) throws SipException, IOException { mLocalProfile = myself; mPassword = password; + mWakeLock = wakeLock; reset(localIp); } @@ -271,7 +275,14 @@ class SipSessionGroup implements SipListener { } } - public void processRequest(RequestEvent event) { + public void processRequest(final RequestEvent event) { + if (isRequestEvent(Request.INVITE, event)) { + if (DEBUG) Log.d(TAG, "<<<<< got INVITE, thread:" + + Thread.currentThread()); + // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME; + // should be large enough to bring up the app. + mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME); + } process(event); } diff --git a/java/com/android/server/sip/SipWakeLock.java b/java/com/android/server/sip/SipWakeLock.java new file mode 100644 index 0000000..52bc094 --- /dev/null +++ b/java/com/android/server/sip/SipWakeLock.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import android.os.PowerManager; +import android.util.Log; + +import java.util.HashSet; + +class SipWakeLock { + private static final boolean DEBUGV = SipService.DEBUGV; + private static final String TAG = SipService.TAG; + private PowerManager mPowerManager; + private PowerManager.WakeLock mWakeLock; + private PowerManager.WakeLock mTimerWakeLock; + private HashSet mHolders = new HashSet(); + + SipWakeLock(PowerManager powerManager) { + mPowerManager = powerManager; + } + + synchronized void reset() { + mHolders.clear(); + release(null); + if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock"); + } + + synchronized void acquire(long timeout) { + if (mTimerWakeLock == null) { + mTimerWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock.timer"); + mTimerWakeLock.setReferenceCounted(true); + } + mTimerWakeLock.acquire(timeout); + } + + synchronized void acquire(Object holder) { + mHolders.add(holder); + if (mWakeLock == null) { + mWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); + } + if (!mWakeLock.isHeld()) mWakeLock.acquire(); + if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count=" + + mHolders.size()); + } + + synchronized void release(Object holder) { + mHolders.remove(holder); + if ((mWakeLock != null) && mHolders.isEmpty() + && mWakeLock.isHeld()) { + mWakeLock.release(); + } + if (DEBUGV) Log.v(TAG, "release wakelock: holder count=" + + mHolders.size()); + } +} -- cgit v1.2.3 From b891d6f795fd5ba90455f8071b03404ff0a5b1aa Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Mon, 18 Oct 2010 15:57:24 +0800 Subject: Set the thread pool size of NIST sip stack to one. Set the thread pool size to one to fix the out-of-order packets seen in sip service when the device is waken up from sleep. bug:http://b/3099715 Change-Id: Ia169e3fde77488068c369e3345ecf6a6d8ddf792 --- java/com/android/server/sip/SipSessionGroup.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index d861fa5..578bd9b 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -82,6 +82,11 @@ class SipSessionGroup implements SipListener { private static final boolean DEBUG = true; private static final boolean DEBUG_PING = DEBUG && false; private static final String ANONYMOUS = "anonymous"; + // Limit the size of thread pool to 1 for the order issue when the phone is + // waken up from sleep and there are many packets to be processed in the SIP + // stack. Note: The default thread pool size in NIST SIP stack is -1 which is + // unlimited. + private static final String THREAD_POOL_SIZE = "1"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds @@ -129,6 +134,7 @@ class SipSessionGroup implements SipListener { SipFactory sipFactory = SipFactory.getInstance(); Properties properties = new Properties(); properties.setProperty("javax.sip.STACK_NAME", getStackName()); + properties.setProperty("javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); String outboundProxy = myself.getProxyAddress(); if (!TextUtils.isEmpty(outboundProxy)) { Log.v(TAG, "outboundProxy is " + outboundProxy); -- cgit v1.2.3 From f33885aab3e3c63024474722a9d8eb498b3db06a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 18 Oct 2010 19:47:33 +0800 Subject: Uncomment SIP/VOIP feature check in SipManager. http://b/issue?id=2971947 Change-Id: I3afa8eb03c4e347b382213dd388354365f766b2f --- java/android/net/sip/SipManager.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index ee0e3cd..2f03e34 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -126,22 +126,16 @@ public class SipManager { * Returns true if the SIP API is supported by the system. */ public static boolean isApiSupported(Context context) { - return true; - /* TODO: uncomment this before ship return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP); - */ } /** * Returns true if the system supports SIP-based VoIP. */ public static boolean isVoipSupported(Context context) { - return true; - /* TODO: uncomment this before ship return context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); - */ } /** -- cgit v1.2.3 From 7f5530b2b6b0183208a3280600de5e74fc1c672c Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Tue, 19 Oct 2010 10:20:21 +0800 Subject: Fix the incorrect environment variable name for the thread pool size. bug: http://b/3099715 Change-Id: I531048414f22c8edcd9c4f815c12a0bdd6347640 --- java/com/android/server/sip/SipSessionGroup.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 578bd9b..bb246a6 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -134,7 +134,8 @@ class SipSessionGroup implements SipListener { SipFactory sipFactory = SipFactory.getInstance(); Properties properties = new Properties(); properties.setProperty("javax.sip.STACK_NAME", getStackName()); - properties.setProperty("javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); + properties.setProperty( + "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); String outboundProxy = myself.getProxyAddress(); if (!TextUtils.isEmpty(outboundProxy)) { Log.v(TAG, "outboundProxy is " + outboundProxy); -- cgit v1.2.3 From 6d9d99615a30de0675271553552c3c7b49311354 Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Mon, 18 Oct 2010 19:13:23 -0400 Subject: Reduce logging. Remember, the system and main logs are - Shared resources - Primarily for recording problems - To be used only for large grained events during normal operation Bug: 3104855 Change-Id: I136fbd101917dcbc8ebc3f96f276426b48bde7b7 --- java/com/android/server/sip/SipService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 1df08c0..a6f0d88 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -68,7 +68,7 @@ import javax.sip.SipException; public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; static final boolean DEBUGV = false; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final boolean DEBUG_TIMER = DEBUG && false; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; @@ -104,7 +104,7 @@ public final class SipService extends ISipService.Stub { if (SipManager.isApiSupported(context)) { ServiceManager.addService("sip", new SipService(context)); context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); - Log.i(TAG, "SIP service started"); + if (DEBUG) Log.i(TAG, "SIP service started"); } } @@ -222,7 +222,7 @@ public final class SipService extends ISipService.Stub { SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return; if (!isCallerCreatorOrRadio(group)) { - Log.d(TAG, "only creator or radio can close this profile"); + Log.w(TAG, "only creator or radio can close this profile"); return; } @@ -244,7 +244,7 @@ public final class SipService extends ISipService.Stub { if (isCallerCreatorOrRadio(group)) { return group.isOpened(); } else { - Log.i(TAG, "only creator or radio can query on the profile"); + Log.w(TAG, "only creator or radio can query on the profile"); return false; } } @@ -257,7 +257,7 @@ public final class SipService extends ISipService.Stub { if (isCallerCreatorOrRadio(group)) { return group.isRegistered(); } else { - Log.i(TAG, "only creator or radio can query on the profile"); + Log.w(TAG, "only creator or radio can query on the profile"); return false; } } @@ -271,7 +271,7 @@ public final class SipService extends ISipService.Stub { if (isCallerCreator(group)) { group.setListener(listener); } else { - Log.i(TAG, "only creator can set listener on the profile"); + Log.w(TAG, "only creator can set listener on the profile"); } } @@ -285,7 +285,7 @@ public final class SipService extends ISipService.Stub { SipSessionGroupExt group = createGroup(localProfile); return group.createSession(listener); } catch (SipException e) { - Log.w(TAG, "createSession()", e); + if (DEBUG) Log.d(TAG, "createSession()", e); return null; } } @@ -303,7 +303,7 @@ public final class SipService extends ISipService.Stub { s.connect(InetAddress.getByName("192.168.1.1"), 80); return s.getLocalAddress().getHostAddress(); } catch (IOException e) { - Log.w(TAG, "determineLocalIp()", e); + if (DEBUG) Log.d(TAG, "determineLocalIp()", e); // dont do anything; there should be a connectivity change going return null; } @@ -467,7 +467,7 @@ public final class SipService extends ISipService.Stub { return createSipSessionGroup(null, localProfile, password); } else { // recursive - Log.wtf(TAG, "impossible!"); + Log.wtf(TAG, "impossible! recursive!"); throw new RuntimeException("createSipSessionGroup"); } } -- cgit v1.2.3 From ed3c0fbbd5457f43ff72cec31b8ab0bb3f7a0047 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 18 Oct 2010 21:04:18 +0800 Subject: Handle dialing a SIP call to self. Reply BUSY HERE response so server may redirect the call to the voice mailbox. http://b/issue?id=3103072 http://b/issue?id=3109479 Change-Id: I81f5dd59ad87298dd9dda87084538ee460eabba8 --- java/com/android/server/sip/SipHelper.java | 4 ++++ java/com/android/server/sip/SipService.java | 21 ++++++++++++++++++++- java/com/android/server/sip/SipSessionGroup.java | 16 ++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 2514262..13e6f14 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -365,6 +365,10 @@ class SipHelper { Response response = mMessageFactory.createResponse( Response.BUSY_HERE, request); + if (inviteTransaction == null) { + inviteTransaction = getServerTransaction(event); + } + if (inviteTransaction.getState() != TransactionState.COMPLETED) { if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response); inviteTransaction.sendResponse(response); diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index a6f0d88..2a35fb0 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -430,6 +430,21 @@ public final class SipService extends ISipService.Stub { } } + private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup, + SipSessionGroup.SipSessionImpl ringingSession) { + String callId = ringingSession.getCallId(); + for (SipSessionGroupExt group : mSipGroups.values()) { + if ((group != ringingGroup) && group.containsSession(callId)) { + if (DEBUG) Log.d(TAG, "call self: " + + ringingSession.getLocalProfile().getUriString() + + " -> " + group.getLocalProfile().getUriString()); + return true; + } + } + return false; + } + + private class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; private PendingIntent mIncomingCallPendingIntent; @@ -452,6 +467,10 @@ public final class SipService extends ISipService.Stub { return mSipGroup.getLocalProfile(); } + public boolean containsSession(String callId) { + return mSipGroup.containsSession(callId); + } + // network connectivity is tricky because network can be disconnected // at any instant so need to deal with exceptions carefully even when // you think you are connected @@ -551,7 +570,7 @@ public final class SipService extends ISipService.Stub { (SipSessionGroup.SipSessionImpl) s; synchronized (SipService.this) { try { - if (!isRegistered()) { + if (!isRegistered() || callingSelf(this, session)) { session.endCall(); return; } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index bb246a6..50ce7dc 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -231,6 +231,10 @@ class SipSessionGroup implements SipListener { } } + synchronized boolean containsSession(String callId) { + return mSessionMap.containsKey(callId); + } + private synchronized SipSessionImpl getSipSession(EventObject event) { String key = SipHelper.getCallId(event); SipSessionImpl session = mSessionMap.get(key); @@ -582,6 +586,7 @@ class SipSessionGroup implements SipListener { } private void processCommand(EventObject command) throws SipException { + if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); if (!process(command)) { onError(SipErrorCode.IN_PROGRESS, "cannot initiate a new transaction to execute: " @@ -1050,6 +1055,13 @@ class SipSessionGroup implements SipListener { mSipHelper.sendCancel(mClientTransaction); startSessionTimer(CANCEL_CALL_TIMER); return true; + } else if (isRequestEvent(Request.INVITE, evt)) { + // Call self? Send BUSY HERE so server may redirect the call to + // voice mailbox. + RequestEvent event = (RequestEvent) evt; + mSipHelper.sendInviteBusyHere(event, + event.getServerTransaction()); + return true; } return false; } @@ -1351,6 +1363,10 @@ class SipSessionGroup implements SipListener { return DEBUG; } + private static boolean isLoggable(EventObject evt) { + return isLoggable(null, evt); + } + private static boolean isLoggable(SipSessionImpl s, EventObject evt) { if (!isLoggable(s)) return false; if (evt == null) return false; -- cgit v1.2.3 From 87e304ce2e55588dfbec18406bf910e5adcfa3a4 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Tue, 19 Oct 2010 17:39:51 +0800 Subject: Periodically scan wifi when wifi is not connected and wifi lock is grabbed in SipService. bug: http://b/3077454 Change-Id: I153974325c29e0f927c8eb7fdbc4725aaf10087d --- java/com/android/server/sip/SipService.java | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 2a35fb0..84e0803 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -79,6 +79,7 @@ public final class SipService extends ISipService.Stub { private String mNetworkType; private boolean mConnected; private WakeupTimer mTimer; + private WifiScanProcess mWifiScanProcess; private WifiManager.WifiLock mWifiLock; private boolean mWifiOnly; @@ -371,6 +372,7 @@ public final class SipService extends ISipService.Stub { mContext.getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); mWifiLock.acquire(); + if (!mConnected) startWifiScanner(); } } @@ -379,6 +381,20 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock"); mWifiLock.release(); mWifiLock = null; + stopWifiScanner(); + } + } + + private synchronized void startWifiScanner() { + if (mWifiScanProcess == null) { + mWifiScanProcess = new WifiScanProcess(); + } + mWifiScanProcess.start(); + } + + private synchronized void stopWifiScanner() { + if (mWifiScanProcess != null) { + mWifiScanProcess.stop(); } } @@ -413,8 +429,10 @@ public final class SipService extends ISipService.Stub { for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } + if (isWifi && (mWifiLock != null)) stopWifiScanner(); } else { mMyWakeLock.reset(); // in case there's a leak + if (isWifi && (mWifiLock != null)) startWifiScanner(); } } catch (SipException e) { Log.e(TAG, "onConnectivityChanged()", e); @@ -611,6 +629,36 @@ public final class SipService extends ISipService.Stub { } } + private class WifiScanProcess implements Runnable { + private static final String TAG = "\\WIFI_SCAN/"; + private static final int INTERVAL = 60; + private boolean mRunning = false; + + private WifiManager mWifiManager; + + public void start() { + if (mRunning) return; + mRunning = true; + mTimer.set(INTERVAL * 1000, this); + } + + WifiScanProcess() { + mWifiManager = (WifiManager) + mContext.getSystemService(Context.WIFI_SERVICE); + } + + public void run() { + // scan and associate now + if (DEBUGV) Log.v(TAG, "just wake up here for wifi scanning..."); + mWifiManager.startScanActive(); + } + + public void stop() { + mRunning = false; + mTimer.cancel(this); + } + } + // KeepAliveProcess is controlled by AutoRegistrationProcess. // All methods will be invoked in sync with SipService.this. private class KeepAliveProcess implements Runnable { -- cgit v1.2.3 From 6577ac4cc93ee3670a5aceceb9a95260d41fe570 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 20 Oct 2010 18:07:01 +0800 Subject: Remove ringtone API from SipAudioCall. (watch out auto-merge conflict for SipAudioCall). Bug: 3113033, related CL: https://android-git/g/#change,75185 Change-Id: Ib48d3b990e229e0b341e47e10e76934e1a50d10f --- java/android/net/sip/SipAudioCall.java | 85 ---------------------------------- java/android/net/sip/SipManager.java | 15 +----- 2 files changed, 1 insertion(+), 99 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index f55bade..834ea70 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -18,10 +18,6 @@ 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; @@ -30,8 +26,6 @@ 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; @@ -175,11 +169,6 @@ public class SipAudioCall { 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; @@ -285,8 +274,6 @@ public class SipAudioCall { private synchronized void close(boolean closeRtp) { if (closeRtp) stopCall(RELEASE_SOCKET); - stopRingbackTone(); - stopRinging(); mInCall = false; mHold = false; @@ -366,7 +353,6 @@ public class SipAudioCall { @Override public void onRingingBack(SipSession session) { Log.d(TAG, "sip call ringing back: " + session); - if (!mInCall) startRingbackTone(); Listener listener = mListener; if (listener != null) { try { @@ -403,8 +389,6 @@ public class SipAudioCall { @Override public void onCallEstablished(SipSession session, String sessionDescription) { - stopRingbackTone(); - stopRinging(); mPeerSd = sessionDescription; Log.v(TAG, "onCallEstablished()" + mPeerSd); @@ -533,10 +517,6 @@ public class SipAudioCall { 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); @@ -580,7 +560,6 @@ public class SipAudioCall { */ public void endCall() throws SipException { synchronized (this) { - stopRinging(); stopCall(RELEASE_SOCKET); mInCall = false; @@ -625,7 +604,6 @@ public class SipAudioCall { */ public void answerCall(int timeout) throws SipException { synchronized (this) { - stopRinging(); try { mAudioStream = new AudioStream(InetAddress.getByName( getLocalIp())); @@ -1024,69 +1002,6 @@ public class SipAudioCall { return mSipSession.getLocalIp(); } - - /** - * Enables/disables the ring-back tone. - * - * @param enabled true to enable; false to disable - */ - public void setRingbackToneEnabled(boolean enabled) { - synchronized (this) { - mRingbackToneEnabled = enabled; - } - } - - /** - * Enables/disables the ring tone. - * - * @param enabled true to enable; false to disable - */ - public void setRingtoneEnabled(boolean enabled) { - synchronized (this) { - 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; diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 2f03e34..e1b1d10 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -350,17 +350,6 @@ public class SipManager { } } - /** - * The method calls {@code takeAudioCall(incomingCallIntent, - * listener, true}. - * - * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean) - */ - public SipAudioCall takeAudioCall(Intent incomingCallIntent, - SipAudioCall.Listener listener) throws SipException { - return takeAudioCall(incomingCallIntent, listener, true); - } - /** * Creates a {@link SipAudioCall} to take an incoming call. Before the call * is returned, the listener will receive a @@ -374,8 +363,7 @@ public class SipManager { * @throws SipException if calling the SIP service results in an error */ public SipAudioCall takeAudioCall(Intent incomingCallIntent, - SipAudioCall.Listener listener, boolean ringtoneEnabled) - throws SipException { + SipAudioCall.Listener listener) throws SipException { if (incomingCallIntent == null) return null; String callId = getCallId(incomingCallIntent); @@ -394,7 +382,6 @@ public class SipManager { if (session == null) return null; SipAudioCall call = new SipAudioCall( mContext, session.getLocalProfile()); - call.setRingtoneEnabled(ringtoneEnabled); call.attachCall(new SipSession(session), offerSd); call.setListener(listener); return call; -- cgit v1.2.3 From d3666076203ecc553211f8fa443ef259667ddd5e Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 18 Oct 2010 19:45:59 +0800 Subject: Add permission requirements to SipAudioCall and SipManager javadoc. Bug: 3116259 Change-Id: I00a033794e9d3e1c2d2ccfe4e612cd50003ec2ee --- java/android/net/sip/SipAudioCall.java | 20 +++++++++++++++++++- java/android/net/sip/SipManager.java | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 834ea70..6a4014f 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -41,6 +41,16 @@ import java.util.Map; * facilitates instantiating a {@code SipAudioCall} object for making/receiving * calls. See {@link SipManager#makeAudioCall} and * {@link SipManager#takeAudioCall}. + * + *

    Requires permissions to use this class: + * {@link android.Manifest.permission#INTERNET} and + * {@link android.Manifest.permission#USE_SIP}. + *
    Requires permissions to {@link #startAudio}: + * {@link android.Manifest.permission#RECORD_AUDIO}, + * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and + * {@link android.Manifest.permission#WAKE_LOCK}. + *
    Requires permissions to {@link #setSpeakerMode}: + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. */ public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); @@ -774,7 +784,11 @@ public class SipAudioCall { } } - /** Puts the device to speaker mode. */ + /** + * Puts the device to speaker mode. + *

    Requires permission: + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. + */ public void setSpeakerMode(boolean speakerMode) { synchronized (this) { ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) @@ -874,6 +888,10 @@ public class SipAudioCall { /** * Starts the audio for the established call. This method should be called * after {@link Listener#onCallEstablished} is called. + *

    Requires permission: + * {@link android.Manifest.permission#RECORD_AUDIO}, + * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and + * {@link android.Manifest.permission#WAKE_LOCK}. */ public void startAudio() { try { diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index e1b1d10..38d2b0c 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -50,6 +50,9 @@ import java.text.ParseException; * * {@code SipManager} can only be instantiated if SIP API is supported by the * device. (See {@link #isApiSupported}). + *

    Requires permissions to use this class: + * {@link android.Manifest.permission#INTERNET} and + * {@link android.Manifest.permission#USE_SIP}. */ public class SipManager { /** -- cgit v1.2.3 From 2e85359f74a5280ee733d35ff5f63b3943140632 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 21 Oct 2010 23:39:35 +0800 Subject: RTP: Fix non-zero DC in EchoSuppressor caused while aggregating samples. Rewrite using integer arithmetic to get full 32-bit precision instead of 23-bit in single precision floating-points. Bug: 3029745 Change-Id: If67dcc403923755f403d08bbafb41ebce26e4e8b --- jni/rtp/AudioGroup.cpp | 2 +- jni/rtp/EchoSuppressor.cpp | 203 ++++++++++++++++++++++++--------------------- jni/rtp/EchoSuppressor.h | 27 +++--- 3 files changed, 128 insertions(+), 104 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 9da560a..0c8a725 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -768,7 +768,7 @@ bool AudioGroup::DeviceThread::threadLoop() LOGD("latency: output %d, input %d", track.latency(), record.latency()); // Initialize echo canceler. - EchoSuppressor echo(sampleRate, sampleCount, sampleCount * 2 + + EchoSuppressor echo(sampleCount, (track.latency() + record.latency()) * sampleRate / 1000); // Give device socket a reasonable buffer size. diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index ad63cd6..d5cff6e 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -24,146 +24,163 @@ #include "EchoSuppressor.h" -EchoSuppressor::EchoSuppressor(int sampleRate, int sampleCount, int tailLength) +// It is very difficult to do echo cancellation at this level due to the lack of +// the timing information of the samples being played and recorded. Therefore, +// for the first release only echo suppression is implemented. + +// The algorithm is derived from the "previous works" summarized in +// A new class of doubletalk detectors based on cross-correlation, +// J Benesty, DR Morgan, JH Cho, IEEE Trans. on Speech and Audio Processing. +// The method proposed in that paper is not used because of its high complexity. + +// It is well known that cross-correlation can be computed using convolution, +// but unfortunately not every mobile processor has a (fast enough) FPU. Thus +// we use integer arithmetic as much as possible and do lots of bookkeeping. +// Again, parameters and thresholds are chosen by experiments. + +EchoSuppressor::EchoSuppressor(int sampleCount, int tailLength) { - int scale = 1; - while (tailLength > 200 * scale) { - scale <<= 1; - } - if (scale > sampleCount) { - scale = sampleCount; + tailLength += sampleCount * 4; + + int shift = 0; + while ((sampleCount >> shift) > 1 && (tailLength >> shift) > 256) { + ++shift; } - mScale = scale; + mShift = shift + 4; + mScale = 1 << shift; mSampleCount = sampleCount; - mWindowSize = sampleCount / scale; - mTailLength = (tailLength + scale - 1) / scale; - mRecordLength = (sampleRate + sampleCount - 1) / sampleCount; + mWindowSize = sampleCount >> shift; + mTailLength = tailLength >> shift; + mRecordLength = tailLength * 2 / sampleCount; mRecordOffset = 0; - mXs = new float[mTailLength + mWindowSize]; - memset(mXs, 0, sizeof(float) * (mTailLength + mWindowSize)); - mXYs = new float[mTailLength]; - memset(mXYs, 0, sizeof(float) * mTailLength); - mXXs = new float[mTailLength]; - memset(mXYs, 0, sizeof(float) * mTailLength); - mYY = 0; - - mXYRecords = new float[mRecordLength * mTailLength]; - memset(mXYRecords, 0, sizeof(float) * mRecordLength * mTailLength); - mXXRecords = new float[mRecordLength * mWindowSize]; - memset(mXXRecords, 0, sizeof(float) * mRecordLength * mWindowSize); - mYYRecords = new float[mRecordLength]; - memset(mYYRecords, 0, sizeof(float) * mRecordLength); + mXs = new uint16_t[mTailLength + mWindowSize]; + memset(mXs, 0, sizeof(*mXs) * (mTailLength + mWindowSize)); + mXSums = new uint32_t[mTailLength]; + memset(mXSums, 0, sizeof(*mXSums) * mTailLength); + mX2Sums = new uint32_t[mTailLength]; + memset(mX2Sums, 0, sizeof(*mX2Sums) * mTailLength); + mXRecords = new uint16_t[mRecordLength * mWindowSize]; + memset(mXRecords, 0, sizeof(*mXRecords) * mRecordLength * mWindowSize); + + mYSum = 0; + mY2Sum = 0; + mYRecords = new uint32_t[mRecordLength]; + memset(mYRecords, 0, sizeof(*mYRecords) * mRecordLength); + mY2Records = new uint32_t[mRecordLength]; + memset(mY2Records, 0, sizeof(*mY2Records) * mRecordLength); + + mXYSums = new uint32_t[mTailLength]; + memset(mXYSums, 0, sizeof(*mXYSums) * mTailLength); + mXYRecords = new uint32_t[mRecordLength * mTailLength]; + memset(mXYRecords, 0, sizeof(*mXYRecords) * mRecordLength * mTailLength); mLastX = 0; mLastY = 0; + mWeight = 1.0f / (mRecordLength * mWindowSize); } EchoSuppressor::~EchoSuppressor() { delete [] mXs; - delete [] mXYs; - delete [] mXXs; + delete [] mXSums; + delete [] mX2Sums; + delete [] mXRecords; + delete [] mYRecords; + delete [] mY2Records; + delete [] mXYSums; delete [] mXYRecords; - delete [] mXXRecords; - delete [] mYYRecords; } void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) { - float *records; - // Update Xs. - for (int i = 0; i < mTailLength; ++i) { - mXs[i] = mXs[mWindowSize + i]; + for (int i = mTailLength - 1; i >= 0; --i) { + mXs[i + mWindowSize] = mXs[i]; } - for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) { - float sum = 0; + for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) { + uint32_t sum = 0; for (int k = 0; k < mScale; ++k) { - float x = playbacked[j + k] >> 8; + int32_t x = playbacked[j + k] << 15; mLastX += x; - sum += (mLastX >= 0) ? mLastX : -mLastX; - mLastX = 0.005f * mLastX - x; + sum += ((mLastX >= 0) ? mLastX : -mLastX) >> 15; + mLastX -= (mLastX >> 10) + x; } - mXs[mTailLength - 1 + i] = sum; + mXs[i] = sum >> mShift; } - // Update XXs and XXRecords. - for (int i = 0; i < mTailLength - mWindowSize; ++i) { - mXXs[i] = mXXs[mWindowSize + i]; + // Update XSums, X2Sums, and XRecords. + for (int i = mTailLength - mWindowSize - 1; i >= 0; --i) { + mXSums[i + mWindowSize] = mXSums[i]; + mX2Sums[i + mWindowSize] = mX2Sums[i]; } - records = &mXXRecords[mRecordOffset * mWindowSize]; - for (int i = 0, j = mTailLength - mWindowSize; i < mWindowSize; ++i, ++j) { - float xx = mXs[mTailLength - 1 + i] * mXs[mTailLength - 1 + i]; - mXXs[j] = mXXs[j - 1] + xx - records[i]; - records[i] = xx; - if (mXXs[j] < 0) { - mXXs[j] = 0; - } + uint16_t *xRecords = &mXRecords[mRecordOffset * mWindowSize]; + for (int i = mWindowSize - 1; i >= 0; --i) { + uint16_t x = mXs[i]; + mXSums[i] = mXSums[i + 1] + x - xRecords[i]; + mX2Sums[i] = mX2Sums[i + 1] + x * x - xRecords[i] * xRecords[i]; + xRecords[i] = x; } // Compute Ys. - float ys[mWindowSize]; - for (int i = 0, j = 0; i < mWindowSize; ++i, j += mScale) { - float sum = 0; + uint16_t ys[mWindowSize]; + for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) { + uint32_t sum = 0; for (int k = 0; k < mScale; ++k) { - float y = recorded[j + k] >> 8; + int32_t y = recorded[j + k] << 15; mLastY += y; - sum += (mLastY >= 0) ? mLastY : -mLastY; - mLastY = 0.005f * mLastY - y; + sum += ((mLastY >= 0) ? mLastY : -mLastY) >> 15; + mLastY -= (mLastY >> 10) + y; } - ys[i] = sum; + ys[i] = sum >> mShift; } - // Update YY and YYRecords. - float yy = 0; - for (int i = 0; i < mWindowSize; ++i) { - yy += ys[i] * ys[i]; - } - mYY += yy - mYYRecords[mRecordOffset]; - mYYRecords[mRecordOffset] = yy; - if (mYY < 0) { - mYY = 0; + // Update YSum, Y2Sum, YRecords, and Y2Records. + uint32_t ySum = 0; + uint32_t y2Sum = 0; + for (int i = mWindowSize - 1; i >= 0; --i) { + ySum += ys[i]; + y2Sum += ys[i] * ys[i]; } - - // Update XYs and XYRecords. - records = &mXYRecords[mRecordOffset * mTailLength]; - for (int i = 0; i < mTailLength; ++i) { - float xy = 0; - for (int j = 0;j < mWindowSize; ++j) { - xy += mXs[i + j] * ys[j]; - } - mXYs[i] += xy - records[i]; - records[i] = xy; - if (mXYs[i] < 0) { - mXYs[i] = 0; + mYSum += ySum - mYRecords[mRecordOffset]; + mY2Sum += y2Sum - mY2Records[mRecordOffset]; + mYRecords[mRecordOffset] = ySum; + mY2Records[mRecordOffset] = y2Sum; + + // Update XYSums and XYRecords. + uint32_t *xyRecords = &mXYRecords[mRecordOffset * mTailLength]; + for (int i = mTailLength - 1; i >= 0; --i) { + uint32_t xySum = 0; + for (int j = mWindowSize - 1; j >= 0; --j) { + xySum += mXs[i + j] * ys[j]; } + mXYSums[i] += xySum - xyRecords[i]; + xyRecords[i] = xySum; } - // Computes correlations from XYs, XXs, and YY. - float weight = 1.0f / (mYY + 1); - float correlation = 0; + // Compute correlations. + float corr2 = 0.0f; int latency = 0; - for (int i = 0; i < mTailLength; ++i) { - float c = mXYs[i] * mXYs[i] * weight / (mXXs[i] + 1); - if (c > correlation) { - correlation = c; + float varY = mY2Sum - mWeight * mYSum * mYSum; + for (int i = mTailLength - 1; i >= 0; --i) { + float varX = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i]; + float cov = mXYSums[i] - mWeight * mXSums[i] * mYSum; + float c2 = cov * cov / (varX * varY + 1); + if (c2 > corr2) { + corr2 = c2; latency = i; } } + //LOGI("correlation^2 = %.10f, latency = %d", corr2, latency * mScale); - correlation = sqrtf(correlation); - if (correlation > 0.3f) { - float factor = 1.0f - correlation; - factor *= factor; - factor /= 2.0; // suppress harder + // Do echo suppression. + if (corr2 > 0.1f) { + int factor = (corr2 > 1.0f) ? 0 : (1.0f - sqrtf(corr2)) * 4096; for (int i = 0; i < mSampleCount; ++i) { - recorded[i] *= factor; + recorded[i] = recorded[i] * factor >> 16; } } - //LOGI("latency %5d, correlation %.10f", latency, correlation); - // Increase RecordOffset. ++mRecordOffset; diff --git a/jni/rtp/EchoSuppressor.h b/jni/rtp/EchoSuppressor.h index 85decf5..2f3b593 100644 --- a/jni/rtp/EchoSuppressor.h +++ b/jni/rtp/EchoSuppressor.h @@ -23,11 +23,12 @@ class EchoSuppressor { public: // The sampleCount must be power of 2. - EchoSuppressor(int sampleRate, int sampleCount, int tailLength); + EchoSuppressor(int sampleCount, int tailLength); ~EchoSuppressor(); void run(int16_t *playbacked, int16_t *recorded); private: + int mShift; int mScale; int mSampleCount; int mWindowSize; @@ -35,17 +36,23 @@ private: int mRecordLength; int mRecordOffset; - float *mXs; - float *mXYs; - float *mXXs; - float mYY; + uint16_t *mXs; + uint32_t *mXSums; + uint32_t *mX2Sums; + uint16_t *mXRecords; - float *mXYRecords; - float *mXXRecords; - float *mYYRecords; + uint32_t mYSum; + uint32_t mY2Sum; + uint32_t *mYRecords; + uint32_t *mY2Records; - float mLastX; - float mLastY; + uint32_t *mXYSums; + uint32_t *mXYRecords; + + int32_t mLastX; + int32_t mLastY; + + float mWeight; }; #endif -- cgit v1.2.3 From 3f4d44657ccad3ed02ed0e1354387b14b07dc011 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 22 Oct 2010 09:01:49 +0800 Subject: Clean up pending sessions on incoming call in SipService Bug: 3122186 Change-Id: I25c9aa19d138f6940a29025d54e7bc2ffb7daa29 --- java/com/android/server/sip/SipService.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 84e0803..f480fec 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -441,13 +441,26 @@ public final class SipService extends ISipService.Stub { private synchronized void addPendingSession(ISipSession session) { try { + cleanUpPendingSessions(); mPendingSessions.put(session.getCallId(), session); + if (DEBUG) Log.d(TAG, "#pending sess=" + mPendingSessions.size()); } catch (RemoteException e) { // should not happen with a local call Log.e(TAG, "addPendingSession()", e); } } + private void cleanUpPendingSessions() throws RemoteException { + Map.Entry[] entries = + mPendingSessions.entrySet().toArray( + new Map.Entry[mPendingSessions.size()]); + for (Map.Entry entry : entries) { + if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) { + mPendingSessions.remove(entry.getKey()); + } + } + } + private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup, SipSessionGroup.SipSessionImpl ringingSession) { String callId = ringingSession.getCallId(); @@ -1095,8 +1108,6 @@ public final class SipService extends ISipService.Stub { } } - // TODO: clean up pending SipSession(s) periodically - /** * Timer that can schedule events to occur even when the device is in sleep. * Only used internally in this package. -- cgit v1.2.3 From 0d603e36cb0d81f55bb90fc37784da3221f13920 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 22 Oct 2010 08:02:26 +0800 Subject: Notify SipSessions before closing SIP stack. Bug: 3116480 Change-Id: I748d63382ade250aed27ccb09ea68c76a433fd27 --- java/com/android/server/sip/SipSessionGroup.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 50ce7dc..2fbaee2 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -190,6 +190,7 @@ class SipSessionGroup implements SipListener { public synchronized void close() { Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); + onConnectivityChanged(); mSessionMap.clear(); closeToNotReceiveCalls(); if (mSipStack != null) { -- cgit v1.2.3 From 4136e2ac4eeca9b80a9da223f5256bba03f71c66 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 27 Oct 2010 17:04:46 +0800 Subject: RTP: Pause echo suppressor when far-end volume is low. Bug: 3136725 Change-Id: Ieeedd2836d3028045aacac963f44285491708cc3 --- jni/rtp/EchoSuppressor.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index d5cff6e..f9508d8 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -160,22 +160,27 @@ void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) } // Compute correlations. - float corr2 = 0.0f; int latency = 0; + float corr2 = 0.0f; + float varX = 0.0f; float varY = mY2Sum - mWeight * mYSum * mYSum; for (int i = mTailLength - 1; i >= 0; --i) { - float varX = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i]; float cov = mXYSums[i] - mWeight * mXSums[i] * mYSum; - float c2 = cov * cov / (varX * varY + 1); - if (c2 > corr2) { - corr2 = c2; - latency = i; + if (cov > 0.0f) { + float varXi = mX2Sums[i] - mWeight * mXSums[i] * mXSums[i]; + float corr2i = cov * cov / (varXi * varY + 1); + if (corr2i > corr2) { + varX = varXi; + corr2 = corr2i; + latency = i; + } } } - //LOGI("correlation^2 = %.10f, latency = %d", corr2, latency * mScale); + //LOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, + // latency * mScale); // Do echo suppression. - if (corr2 > 0.1f) { + if (corr2 > 0.1f && varX > 10000.0f) { int factor = (corr2 > 1.0f) ? 0 : (1.0f - sqrtf(corr2)) * 4096; for (int i = 0; i < mSampleCount; ++i) { recorded[i] = recorded[i] * factor >> 16; -- cgit v1.2.3 From 16b441b4ad92c6a5cbc6f27cb3705eaaaaee20c1 Mon Sep 17 00:00:00 2001 From: Scott Main Date: Fri, 22 Oct 2010 11:29:57 -0700 Subject: docs: revise javadocs for sip add a package description, revise class descriptions and edit some method docs Change-Id: Ice969a99c830349674c65d99e4b7a6f1d2f24a7e --- java/android/net/sip/SipAudioCall.java | 59 ++++++++++++----------- java/android/net/sip/SipErrorCode.java | 10 ++-- java/android/net/sip/SipException.java | 2 +- java/android/net/sip/SipManager.java | 57 +++++++++++----------- java/android/net/sip/SipProfile.java | 7 ++- java/android/net/sip/SipRegistrationListener.java | 2 +- java/android/net/sip/SipSession.java | 11 +++-- java/android/net/sip/package.html | 39 +++++++++++++++ 8 files changed, 118 insertions(+), 69 deletions(-) create mode 100644 java/android/net/sip/package.html diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 6a4014f..b847ff6 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -37,20 +37,19 @@ import java.util.List; import java.util.Map; /** - * Class that handles an Internet audio call over SIP. {@link SipManager} - * facilitates instantiating a {@code SipAudioCall} object for making/receiving - * calls. See {@link SipManager#makeAudioCall} and - * {@link SipManager#takeAudioCall}. + * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager}, + * using {@link SipManager#makeAudioCall makeAudioCall()} and {@link SipManager#takeAudioCall + * takeAudioCall()}. * - *

    Requires permissions to use this class: + *

    Note: Using this class require the * {@link android.Manifest.permission#INTERNET} and - * {@link android.Manifest.permission#USE_SIP}. - *
    Requires permissions to {@link #startAudio}: + * {@link android.Manifest.permission#USE_SIP} permissions.

    In addition, {@link + * #startAudio} requires the * {@link android.Manifest.permission#RECORD_AUDIO}, - * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and - * {@link android.Manifest.permission#WAKE_LOCK}. - *
    Requires permissions to {@link #setSpeakerMode}: - * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. + * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and + * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode + * setSpeakerMode()} requires the + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

    */ public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); @@ -58,7 +57,10 @@ public class SipAudioCall { private static final boolean DONT_RELEASE_SOCKET = false; private static final int SESSION_TIMEOUT = 5; // in seconds - /** Listener class for all event callbacks. */ + /** Listener for events relating to a SIP call, such as when a call is being + * recieved ("on ringing") or a call is outgoing ("on calling"). + *

    Many of these events are also received by {@link SipSession.Listener}.

    + */ public static class Listener { /** * Called when the call object is ready to make another call. @@ -199,7 +201,7 @@ public class SipAudioCall { /** * Sets the listener to listen to the audio call events. The method calls - * {@code setListener(listener, false)}. + * {@link #setListener setListener(listener, false)}. * * @param listener to listen to the audio call events of this object * @see #setListener(Listener, boolean) @@ -537,14 +539,14 @@ public class SipAudioCall { /** * 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)} + * and {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @param peerProfile the SIP profile to make the call to * @param sipSession the {@link SipSession} for carrying out the call * @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 + * @see Listener#onError * @throws SipException if the SIP service fails to create a session for the * call */ @@ -582,12 +584,12 @@ public class SipAudioCall { * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is * called. 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)} + * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @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 + * @see Listener#onError * @throws SipException if the SIP service fails to hold the call */ public void holdCall(int timeout) throws SipException { @@ -604,12 +606,12 @@ public class SipAudioCall { /** * Answers a call. 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)} + * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @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 + * @see Listener#onError * @throws SipException if the SIP service fails to answer the call */ public void answerCall(int timeout) throws SipException { @@ -628,12 +630,12 @@ public class SipAudioCall { * Continues a call that's on hold. When succeeds, * {@link Listener#onCallEstablished} is called. 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)} + * {@link Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @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 + * @see Listener#onError * @throws SipException if the SIP service fails to unhold the call */ public void continueCall(int timeout) throws SipException { @@ -786,8 +788,8 @@ public class SipAudioCall { /** * Puts the device to speaker mode. - *

    Requires permission: - * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}. + *

    Note: Requires the + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

    */ public void setSpeakerMode(boolean speakerMode) { synchronized (this) { @@ -797,20 +799,21 @@ public class SipAudioCall { } /** - * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal + * Sends a DTMF code. According to RFC 2883, + * 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. Value 0 to 15 (inclusive) are valid * inputs. - * @see http://tools.ietf.org/html/rfc2833 */ public void sendDtmf(int code) { sendDtmf(code, null); } /** - * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal + * Sends a DTMF code. According to RFC 2883, + * 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. * @@ -888,10 +891,10 @@ public class SipAudioCall { /** * Starts the audio for the established call. This method should be called * after {@link Listener#onCallEstablished} is called. - *

    Requires permission: + *

    Note: Requires the * {@link android.Manifest.permission#RECORD_AUDIO}, * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and - * {@link android.Manifest.permission#WAKE_LOCK}. + * {@link android.Manifest.permission#WAKE_LOCK} permissions.

    */ public void startAudio() { try { diff --git a/java/android/net/sip/SipErrorCode.java b/java/android/net/sip/SipErrorCode.java index 6aee5f1..509728f 100644 --- a/java/android/net/sip/SipErrorCode.java +++ b/java/android/net/sip/SipErrorCode.java @@ -17,11 +17,11 @@ package android.net.sip; /** - * Defines error code returned in - * {@link SipRegistrationListener#onRegistrationFailed}, - * {@link SipSession.Listener#onError}, - * {@link SipSession.Listener#onCallChangeFailed} and - * {@link SipSession.Listener#onRegistrationFailed}. + * Defines error codes returned during SIP actions. For example, during + * {@link SipRegistrationListener#onRegistrationFailed onRegistrationFailed()}, + * {@link SipSession.Listener#onError onError()}, + * {@link SipSession.Listener#onCallChangeFailed onCallChangeFailed()} and + * {@link SipSession.Listener#onRegistrationFailed onRegistrationFailed()}. */ public class SipErrorCode { /** Not an error. */ diff --git a/java/android/net/sip/SipException.java b/java/android/net/sip/SipException.java index 225b94f..0339395 100644 --- a/java/android/net/sip/SipException.java +++ b/java/android/net/sip/SipException.java @@ -17,7 +17,7 @@ package android.net.sip; /** - * General SIP-related exception class. + * Indicates a general SIP-related exception. */ public class SipException extends Exception { public SipException() { diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 38d2b0c..8aaa805 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -29,30 +29,29 @@ import android.util.Log; import java.text.ParseException; /** - * The class provides API for various SIP related tasks. Specifically, the API - * allows an application to: + * Provides APIs for SIP tasks, such as initiating SIP connections, and provides access to related + * SIP services. This class is the starting point for any SIP actions. You can acquire an instance + * of it with {@link #newInstance newInstance()}.

    + *

    The APIs in this class allows you to:

    *
      - *
    • 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, PendingIntent, SipRegistrationListener)}, - * {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and - * {@link #isRegistered}. It also facilitates handling of the incoming call - * broadcast intent. See - * {@link #isIncomingCallIntent}, {@link #getCallId}, - * {@link #getOfferSessionDescription} and {@link #takeAudioCall}.
    • - *
    • make/take SIP-based audio calls. See - * {@link #makeAudioCall} and {@link #takeAudioCall}.
    • - *
    • register/unregister with a SIP service provider manually. See - * {@link #register} and {@link #unregister}.
    • - *
    • process SIP events directly with a {@link SipSession} created by - * {@link #createSipSession}.
    • + *
    • Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See + * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.
    • + *
    • Initiate and receive generic SIP calls or audio-only SIP calls. Generic SIP calls may + * be video, audio, or other, and are initiated with {@link #open open()}. Audio-only SIP calls + * should be handled with a {@link SipAudioCall}, which you can acquire with {@link + * #makeAudioCall makeAudioCall()} and {@link #takeAudioCall takeAudioCall()}.
    • + *
    • Register and unregister with a SIP service provider, with + * {@link #register register()} and {@link #unregister unregister()}.
    • + *
    • Verify session connectivity, with {@link #isOpened isOpened()} and + * {@link #isRegistered isRegistered()}.
    • *
    - * {@code SipManager} can only be instantiated if SIP API is supported by the - * device. (See {@link #isApiSupported}). - *

    Requires permissions to use this class: - * {@link android.Manifest.permission#INTERNET} and - * {@link android.Manifest.permission#USE_SIP}. + *

    Note: Not all Android-powered devices support VOIP calls using + * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported + * isVoipSupported()} to verify that the device supports VOIP calling and {@link + * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports + * the SIP APIs.

    Your application must also request the {@link + * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP} + * permissions.

    */ public class SipManager { /** @@ -160,7 +159,7 @@ public class SipManager { } /** - * Opens the profile for making calls. The caller may make subsequent calls + * Opens the profile for making generic SIP calls. The caller may make subsequent calls * through {@link #makeAudioCall}. If one also wants to receive calls on the * profile, use * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} @@ -179,7 +178,7 @@ public class SipManager { } /** - * Opens the profile for making calls and/or receiving calls. The caller may + * Opens the profile for making calls and/or receiving generic SIP 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 @@ -296,7 +295,7 @@ public class SipManager { /** * Creates a {@link SipAudioCall} to make a call. The attempt will be timed * out if the call is not established within {@code timeout} seconds and - * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @param localProfile the SIP profile to make the call from @@ -307,7 +306,7 @@ public class SipManager { * SIP protocol) is used if {@code timeout} is zero or negative. * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error - * @see SipAudioCall.Listener.onError + * @see SipAudioCall.Listener#onError */ public SipAudioCall makeAudioCall(SipProfile localProfile, SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) @@ -327,7 +326,7 @@ public class SipManager { * Creates a {@link SipAudioCall} to make an audio call. The attempt will be * timed out if the call is not established within {@code timeout} seconds * and - * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} + * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} * will be called. * * @param localProfileUri URI of the SIP profile to make the call from @@ -338,7 +337,7 @@ public class SipManager { * SIP protocol) is used if {@code timeout} is zero or negative. * @return a {@link SipAudioCall} object * @throws SipException if calling the SIP service results in an error - * @see SipAudioCall.Listener.onError + * @see SipAudioCall.Listener#onError */ public SipAudioCall makeAudioCall(String localProfileUri, String peerProfileUri, SipAudioCall.Listener listener, int timeout) @@ -449,7 +448,7 @@ public class SipManager { * receiving calls. * {@link #open(SipProfile, PendingIntent, SipRegistrationListener)} is * still needed to be called at least once in order for the SIP service to - * notify the caller with the {@code PendingIntent} when an incoming call is + * notify the caller with the {@link android.app.PendingIntent} when an incoming call is * received. * * @param localProfile the SIP profile to register with diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index dddb07d..6977e30 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -32,7 +32,10 @@ import javax.sip.address.SipURI; import javax.sip.address.URI; /** - * Class containing a SIP account, domain and server information. + * Defines a SIP profile, including a SIP account, domain and server information. + *

    You can create a {@link SipProfile} using {@link + * SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link + * SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.

    */ public class SipProfile implements Parcelable, Serializable, Cloneable { private static final long serialVersionUID = 1L; @@ -59,7 +62,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { }; /** - * Class to help create a {@code SipProfile}. + * Helper class for creating a {@link SipProfile}. */ public static class Builder { private AddressFactory mAddressFactory; diff --git a/java/android/net/sip/SipRegistrationListener.java b/java/android/net/sip/SipRegistrationListener.java index e1f35ad..9968cc7 100644 --- a/java/android/net/sip/SipRegistrationListener.java +++ b/java/android/net/sip/SipRegistrationListener.java @@ -17,7 +17,7 @@ package android.net.sip; /** - * Listener class to listen to SIP registration events. + * Listener for SIP registration events. */ public interface SipRegistrationListener { /** diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java index 9c08e46..5629b3c 100644 --- a/java/android/net/sip/SipSession.java +++ b/java/android/net/sip/SipSession.java @@ -20,14 +20,17 @@ import android.os.RemoteException; import android.util.Log; /** - * A SIP session that is associated with a SIP dialog or a standalone + * Represents a SIP session that is associated with a SIP dialog or a standalone * transaction not within a dialog. + *

    You can get a {@link SipSession} from {@link SipManager} with {@link + * SipManager#createSipSession createSipSession()} (when initiating calls) or {@link + * SipManager#getSessionFor getSessionFor()} (when receiving calls).

    */ public final class SipSession { private static final String TAG = "SipSession"; /** - * Defines {@link SipSession} states. + * Defines SIP session states, such as "registering", "outgoing call", and "in call". */ public static class State { /** When session is ready to initiate a call or transaction. */ @@ -98,7 +101,9 @@ public final class SipSession { } /** - * Listener class that listens to {@link SipSession} events. + * Listener for events relating to a SIP session, such as when a session is being registered + * ("on registering") or a call is outgoing ("on calling"). + *

    Many of these events are also received by {@link SipAudioCall.Listener}.

    */ public static class Listener { /** diff --git a/java/android/net/sip/package.html b/java/android/net/sip/package.html new file mode 100644 index 0000000..790656b --- /dev/null +++ b/java/android/net/sip/package.html @@ -0,0 +1,39 @@ + + +

    Provides access to Session Initiation Protocol (SIP) functionality, such as +making and answering VOIP calls using SIP.

    + +

    To get started, you need to get an instance of the {@link android.net.sip.SipManager} by +calling {@link android.net.sip.SipManager#newInstance newInstance()}.

    + +

    With the {@link android.net.sip.SipManager}, you can initiate SIP audio calls with {@link +android.net.sip.SipManager#makeAudioCall makeAudioCall()} and {@link +android.net.sip.SipManager#takeAudioCall takeAudioCall()}. Both methods require +a {@link android.net.sip.SipAudioCall.Listener} that receives callbacks when the state of the +call changes, such as when the call is ringing, established, or ended.

    + +

    Both {@link android.net.sip.SipManager#makeAudioCall makeAudioCall()} also requires two +{@link android.net.sip.SipProfile} objects, representing the local device and the peer +device. You can create a {@link android.net.sip.SipProfile} using the {@link +android.net.sip.SipProfile.Builder} subclass.

    + +

    Once you have a {@link android.net.sip.SipAudioCall}, you can perform SIP audio call actions with +the instance, such as make a call, answer a call, mute a call, turn on speaker mode, send DTMF +tones, and more.

    + +

    If you want to create generic SIP connections (such as for video calls or other), you can +create a SIP connection from the {@link android.net.sip.SipManager}, using {@link +android.net.sip.SipManager#open open()}. If you only want to create audio SIP calls, though, you +should use the {@link android.net.sip.SipAudioCall} class, as described above.

    + +

    Note: +Not all Android-powered devices support VOIP functionality with SIP. Before performing any SIP +activity, you should call {@link android.net.sip.SipManager#isVoipSupported isVoipSupported()} +to verify that the device supports VOIP calling and {@link +android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports the +SIP APIs.

    +Your application must also request the {@link android.Manifest.permission#INTERNET} and {@link +android.Manifest.permission#USE_SIP} permissions in order to use the SIP APIs. +

    + + \ No newline at end of file -- cgit v1.2.3 From 617479363dcafe01bfa1fa025e1d9d122864a0ce Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 2 Nov 2010 15:15:43 +0800 Subject: Correct SipService.isOpened() implementation. Make it return true for all existing accounts. Rename mOpened to mOpenedToReceiveCalls to make it less confusing. Bug: 3155849 Change-Id: I327f411bf76afd73434ad1fa2ffef3db1e35d778 --- java/com/android/server/sip/SipService.java | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index f480fec..3af6e78 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -135,7 +135,7 @@ public final class SipService extends ISipService.Stub { switch (state) { case WifiManager.WIFI_STATE_ENABLED: mWifiEnabled = true; - if (anyOpened()) grabWifiLock(); + if (anyOpenedToReceiveCalls()) grabWifiLock(); break; case WifiManager.WIFI_STATE_DISABLED: mWifiEnabled = false; @@ -231,7 +231,7 @@ public final class SipService extends ISipService.Stub { notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (!anyOpened()) { + if (!anyOpenedToReceiveCalls()) { releaseWifiLock(); mMyWakeLock.reset(); // in case there's leak } @@ -243,7 +243,7 @@ public final class SipService extends ISipService.Stub { SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return false; if (isCallerCreatorOrRadio(group)) { - return group.isOpened(); + return true; } else { Log.w(TAG, "only creator or radio can query on the profile"); return false; @@ -358,9 +358,9 @@ public final class SipService extends ISipService.Stub { mContext.sendBroadcast(intent); } - private boolean anyOpened() { + private boolean anyOpenedToReceiveCalls() { for (SipSessionGroupExt group : mSipGroups.values()) { - if (group.isOpened()) return true; + if (group.isOpenedToReceiveCalls()) return true; } return false; } @@ -479,7 +479,7 @@ public final class SipService extends ISipService.Stub { private class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; private PendingIntent mIncomingCallPendingIntent; - private boolean mOpened; + private boolean mOpenedToReceiveCalls; private AutoRegistrationProcess mAutoRegistration = new AutoRegistrationProcess(); @@ -541,7 +541,7 @@ public final class SipService extends ISipService.Stub { } public void openToReceiveCalls() throws SipException { - mOpened = true; + mOpenedToReceiveCalls = true; if (mConnected) { mSipGroup.openToReceiveCalls(this); mAutoRegistration.start(mSipGroup); @@ -555,9 +555,9 @@ public final class SipService extends ISipService.Stub { mSipGroup.onConnectivityChanged(); if (connected) { resetGroup(mLocalIp); - if (mOpened) openToReceiveCalls(); + if (mOpenedToReceiveCalls) openToReceiveCalls(); } else { - // close mSipGroup but remember mOpened + // close mSipGroup but remember mOpenedToReceiveCalls if (DEBUG) Log.d(TAG, " close auto reg temporarily: " + getUri() + ": " + mIncomingCallPendingIntent); mSipGroup.close(); @@ -582,7 +582,7 @@ public final class SipService extends ISipService.Stub { } public void close() { - mOpened = false; + mOpenedToReceiveCalls = false; mSipGroup.close(); mAutoRegistration.stop(); if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " @@ -629,8 +629,8 @@ public final class SipService extends ISipService.Stub { + SipErrorCode.toString(errorCode) + ": " + message); } - public boolean isOpened() { - return mOpened; + public boolean isOpenedToReceiveCalls() { + return mOpenedToReceiveCalls; } public boolean isRegistered() { -- cgit v1.2.3 From b030e52a52acaf28ce68b1010aac007af909478d Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 3 Nov 2010 11:50:05 +0800 Subject: Throw proper exceptions in SipManager instead of silently returning null and causing NPE in applications as returning null is not documented in the javadoc. Add connection to the connection list in SipCall after dial() succeeds so that we don't need to clean up if it fails. The original code will cause the failed connection to continue to live in the SipCall and in next dial() attempt, a new connection is created and the in-call screen sees two connections in the call and thus shows conference call UI. Bug: 3157234, 3157387 Change-Id: Iabc3235f781c4f1e09384a67ad56b09ad2c12e5e --- java/android/net/sip/SipManager.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 8aaa805..2e38662 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -314,10 +314,6 @@ public class SipManager { SipAudioCall call = new SipAudioCall(mContext, localProfile); call.setListener(listener); SipSession s = createSipSession(localProfile, null); - if (s == null) { - throw new SipException( - "Failed to create SipSession; network available?"); - } call.makeCall(peerProfile, s, timeout); return call; } @@ -366,7 +362,9 @@ public class SipManager { */ public SipAudioCall takeAudioCall(Intent incomingCallIntent, SipAudioCall.Listener listener) throws SipException { - if (incomingCallIntent == null) return null; + if (incomingCallIntent == null) { + throw new SipException("Cannot retrieve session with null intent"); + } String callId = getCallId(incomingCallIntent); if (callId == null) { @@ -381,7 +379,9 @@ public class SipManager { try { ISipSession session = mSipService.getPendingSession(callId); - if (session == null) return null; + if (session == null) { + throw new SipException("No pending session for the call"); + } SipAudioCall call = new SipAudioCall( mContext, session.getLocalProfile()); call.attachCall(new SipSession(session), offerSd); @@ -526,6 +526,10 @@ public class SipManager { SipSession.Listener listener) throws SipException { try { ISipSession s = mSipService.createSession(localProfile, null); + if (s == null) { + throw new SipException( + "Failed to create SipSession; network unavailable?"); + } return new SipSession(s, listener); } catch (RemoteException e) { throw new SipException("createSipSession()", e); @@ -541,7 +545,7 @@ public class SipManager { try { return mSipService.getListOfProfiles(); } catch (RemoteException e) { - return null; + return new SipProfile[0]; } } -- cgit v1.2.3 From bddf530f0bbd2917d98c3fd0f11920c2b2473154 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 25 Oct 2010 12:08:43 +0800 Subject: Do not suppress error feedback during a SIP call. Bug: 3124788 Change-Id: Ia0a06f72336d1795515428eba0c9f875c32d13d1 --- java/com/android/server/sip/SipSessionGroup.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 2fbaee2..29f5f5a 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1163,11 +1163,6 @@ class SipSessionGroup implements SipListener { mProxy.onCallEstablished(this, mPeerSessionDescription); } - private void fallbackToPreviousInCall(int errorCode, String message) { - mState = SipSession.State.IN_CALL; - mProxy.onCallChangeFailed(this, errorCode, message); - } - private void endCallNormally() { reset(); mProxy.onCallEnded(this); @@ -1191,12 +1186,7 @@ class SipSessionGroup implements SipListener { onRegistrationFailed(errorCode, message); break; default: - if ((errorCode != SipErrorCode.DATA_CONNECTION_LOST) - && mInCall) { - fallbackToPreviousInCall(errorCode, message); - } else { - endCallOnError(errorCode, message); - } + endCallOnError(errorCode, message); } } -- cgit v1.2.3 From 44afaba6d2381a83caf6d6a5f3b1982d47572f3c Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 25 Oct 2010 17:23:10 +0800 Subject: Set AudioGroup mode according to audio settings Set AudioGroup mode according to holding, mute and speaker phone settings. Bug: 3119690 Change-Id: I02803ae105409b7f8482e6c2ef3e67623bd54e03 --- java/android/net/sip/SipAudioCall.java | 49 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index b847ff6..286f60b 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -594,12 +594,10 @@ public class SipAudioCall { */ public void holdCall(int timeout) throws SipException { synchronized (this) { - if (mHold) return; + if (mHold) return; mSipSession.changeCall(createHoldOffer().encode(), timeout); mHold = true; - - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + setAudioGroupMode(); } } @@ -643,8 +641,7 @@ public class SipAudioCall { if (!mHold) return; mSipSession.changeCall(createContinueOffer().encode(), timeout); mHold = false; - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL); + setAudioGroupMode(); } } @@ -765,13 +762,8 @@ public class SipAudioCall { /** Toggles mute. */ public void toggleMute() { synchronized (this) { - AudioGroup audioGroup = getAudioGroup(); - if (audioGroup != null) { - audioGroup.setMode(mMuted - ? AudioGroup.MODE_NORMAL - : AudioGroup.MODE_MUTED); - mMuted = !mMuted; - } + mMuted = !mMuted; + setAudioGroupMode(); } } @@ -790,14 +782,22 @@ public class SipAudioCall { * Puts the device to speaker mode. *

    Note: Requires the * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

    + * + * @param speakerMode set true to enable speaker mode; false to disable */ public void setSpeakerMode(boolean speakerMode) { synchronized (this) { ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) .setSpeakerphoneOn(speakerMode); + setAudioGroupMode(); } } + private boolean isSpeakerOn() { + return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) + .isSpeakerphoneOn(); + } + /** * Sends a DTMF code. According to RFC 2883, * event 0--9 maps to decimal @@ -874,7 +874,11 @@ public class SipAudioCall { /** * Sets the {@link AudioGroup} object which the {@link AudioStream} object * joins. If {@code audioGroup} is null, then the {@code AudioGroup} object - * will be dynamically created when needed. + * will be dynamically created when needed. Note that the mode of the + * {@code AudioGroup} is not changed according to the audio settings (i.e., + * hold, mute, speaker phone) of this object. This is mainly used to merge + * multiple {@code SipAudioCall} objects to form a conference call. The + * settings of the first object (that merges others) override others'. * * @see #getAudioStream * @hide @@ -990,16 +994,25 @@ public class SipAudioCall { // 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) { + } + setAudioGroupMode(); + } + + // set audio group mode based on current audio configuration + private void setAudioGroupMode() { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) { + if (mHold) { + audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } else if (mMuted) { audioGroup.setMode(AudioGroup.MODE_MUTED); + } else if (isSpeakerOn()) { + audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); } else { audioGroup.setMode(AudioGroup.MODE_NORMAL); } -- cgit v1.2.3 From a54e36694b3909ef367867eb7516b2d6f6031261 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 30 Nov 2010 13:10:31 +0800 Subject: RTP: Prepare to unhide the APIs. Polish things a little bit. Change-Id: I2c3cea8b34b9c858879bc722ea1f38082ba22b8d --- java/android/net/rtp/AudioGroup.java | 110 ++++++++++++++++++++++++---------- java/android/net/rtp/AudioStream.java | 28 +++++---- java/android/net/rtp/RtpStream.java | 7 ++- jni/rtp/AmrCodec.cpp | 2 +- jni/rtp/AudioGroup.cpp | 18 ++---- 5 files changed, 107 insertions(+), 58 deletions(-) diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index 43a3827..a6b54d8 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -21,14 +21,14 @@ import java.util.Map; /** * An AudioGroup acts as a router connected to the speaker, the microphone, and - * {@link AudioStream}s. Its pipeline has four steps. First, for each - * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming - * packets and stores in its buffer. Then, if the microphone is enabled, - * processes the recorded audio and stores in its buffer. Third, if the speaker - * is enabled, mixes and playbacks buffers of all AudioStreams. Finally, for - * each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other - * buffers and sends back the encoded packets. An AudioGroup does nothing if - * there is no AudioStream in it. + * {@link AudioStream}s. Its execution loop consists of four steps. First, for + * each AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its + * incoming packets and stores in its buffer. Then, if the microphone is + * enabled, processes the recorded audio and stores in its buffer. Third, if the + * speaker is enabled, mixes and playbacks buffers of all AudioStreams. Finally, + * for each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all + * other buffers and sends back the encoded packets. An AudioGroup does nothing + * if there is no AudioStream in it. * *

    Few things must be noticed before using these classes. The performance is * highly related to the system load and the network bandwidth. Usually a @@ -47,7 +47,12 @@ import java.util.Map; * modes other than {@link #MODE_ON_HOLD}. In addition, before adding an * AudioStream into an AudioGroup, one should always put all other AudioGroups * into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly - * initialized. + * initialized.

    + * + *

    Using this class requires + * {@link android.Manifest.permission#RECORD_AUDIO} permission.

    + * + * @see AudioStream * @hide */ public class AudioGroup { @@ -78,6 +83,8 @@ public class AudioGroup { */ public static final int MODE_ECHO_SUPPRESSION = 3; + private static final int MODE_LAST = 3; + private final Map mStreams; private int mMode = MODE_ON_HOLD; @@ -93,6 +100,15 @@ public class AudioGroup { mStreams = new HashMap(); } + /** + * Returns the {@link AudioStream}s in this group. + */ + public AudioStream[] getStreams() { + synchronized (this) { + return mStreams.keySet().toArray(new AudioStream[mStreams.size()]); + } + } + /** * Returns the current mode. */ @@ -108,49 +124,77 @@ public class AudioGroup { * @param mode The mode to change to. * @throws IllegalArgumentException if the mode is invalid. */ - public synchronized native void setMode(int mode); - - private native void add(int mode, int socket, String remoteAddress, - int remotePort, String codecSpec, int dtmfType); + public void setMode(int mode) { + if (mode < 0 || mode > MODE_LAST) { + throw new IllegalArgumentException("Invalid mode"); + } + synchronized (this) { + nativeSetMode(mode); + mMode = mode; + } + } - synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { - if (!mStreams.containsKey(stream)) { - try { - int socket = stream.dup(); - String codecSpec = String.format("%d %s %s", codec.type, - codec.rtpmap, codec.fmtp); - add(stream.getMode(), socket, - stream.getRemoteAddress().getHostAddress(), - stream.getRemotePort(), codecSpec, dtmfType); - mStreams.put(stream, socket); - } catch (NullPointerException e) { - throw new IllegalStateException(e); + private native void nativeSetMode(int mode); + + // Package-private method used by AudioStream.join(). + void add(AudioStream stream, AudioCodec codec, int dtmfType) { + synchronized (this) { + if (!mStreams.containsKey(stream)) { + try { + int socket = stream.dup(); + String codecSpec = String.format("%d %s %s", codec.type, + codec.rtpmap, codec.fmtp); + nativeAdd(stream.getMode(), socket, + stream.getRemoteAddress().getHostAddress(), + stream.getRemotePort(), codecSpec, dtmfType); + mStreams.put(stream, socket); + } catch (NullPointerException e) { + throw new IllegalStateException(e); + } } } } - private native void remove(int socket); + private native void nativeAdd(int mode, int socket, String remoteAddress, + int remotePort, String codecSpec, int dtmfType); - synchronized void remove(AudioStream stream) { - Integer socket = mStreams.remove(stream); - if (socket != null) { - remove(socket); + // Package-private method used by AudioStream.join(). + void remove(AudioStream stream) { + synchronized (this) { + Integer socket = mStreams.remove(stream); + if (socket != null) { + nativeRemove(socket); + } } } + private native void nativeRemove(int socket); + /** * Sends a DTMF digit to every {@link AudioStream} in this group. Currently * only event {@code 0} to {@code 15} are supported. * * @throws IllegalArgumentException if the event is invalid. */ - public native synchronized void sendDtmf(int event); + public void sendDtmf(int event) { + if (event < 0 || event > 15) { + throw new IllegalArgumentException("Invalid event"); + } + synchronized (this) { + nativeSendDtmf(event); + } + } + + private native void nativeSendDtmf(int event); /** * Removes every {@link AudioStream} in this group. */ - public synchronized void clear() { - remove(-1); + public void clear() { + synchronized (this) { + mStreams.clear(); + nativeRemove(-1); + } } @Override diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index e5197ce..0edae6b 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -34,8 +34,12 @@ import java.net.SocketException; * of the setter methods are disabled. This is designed to ease the task of * managing native resources. One can always make an AudioStream leave its * AudioGroup by calling {@link #join(AudioGroup)} with {@code null} and put it - * back after the modification is done. + * back after the modification is done.

    * + *

    Using this class requires + * {@link android.Manifest.permission#INTERNET} permission.

    + * + * @see RtpStream * @see AudioGroup * @hide */ @@ -82,16 +86,18 @@ public class AudioStream extends RtpStream { * @see AudioGroup */ public void join(AudioGroup group) { - if (mGroup == group) { - return; - } - if (mGroup != null) { - mGroup.remove(this); - mGroup = null; - } - if (group != null) { - group.add(this, mCodec, mDtmfType); - mGroup = group; + synchronized (this) { + if (mGroup == group) { + return; + } + if (mGroup != null) { + mGroup.remove(this); + mGroup = null; + } + if (group != null) { + group.add(this, mCodec, mDtmfType); + mGroup = group; + } } } diff --git a/java/android/net/rtp/RtpStream.java b/java/android/net/rtp/RtpStream.java index 23fb258..87d8bc6 100644 --- a/java/android/net/rtp/RtpStream.java +++ b/java/android/net/rtp/RtpStream.java @@ -24,6 +24,9 @@ import java.net.SocketException; /** * RtpStream represents the base class of streams which send and receive network * packets with media payloads over Real-time Transport Protocol (RTP). + * + *

    Using this class requires + * {@link android.Manifest.permission#INTERNET} permission.

    * @hide */ public class RtpStream { @@ -43,6 +46,8 @@ public class RtpStream { */ public static final int MODE_RECEIVE_ONLY = 2; + private static final int MODE_LAST = 2; + private final InetAddress mLocalAddress; private final int mLocalPort; @@ -129,7 +134,7 @@ public class RtpStream { if (isBusy()) { throw new IllegalStateException("Busy"); } - if (mode != MODE_NORMAL && mode != MODE_SEND_ONLY && mode != MODE_RECEIVE_ONLY) { + if (mode < 0 || mode > MODE_LAST) { throw new IllegalArgumentException("Invalid mode"); } mMode = mode; diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp index 72ee44e..84c7166 100644 --- a/jni/rtp/AmrCodec.cpp +++ b/jni/rtp/AmrCodec.cpp @@ -73,7 +73,7 @@ int AmrCodec::set(int sampleRate, const char *fmtp) } // Handle mode-set and octet-align. - char *modes = (char*)strcasestr(fmtp, "mode-set="); + const char *modes = strcasestr(fmtp, "mode-set="); if (modes) { mMode = 0; mModeSet = 0; diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 0c8a725..cba1123 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -90,6 +90,7 @@ public: void encode(int tick, AudioStream *chain); void decode(int tick); +private: enum { NORMAL = 0, SEND_ONLY = 1, @@ -97,7 +98,6 @@ public: LAST_MODE = 2, }; -private: int mMode; int mSocket; sockaddr_storage mRemote; @@ -463,6 +463,7 @@ public: bool add(AudioStream *stream); bool remove(int socket); +private: enum { ON_HOLD = 0, MUTED = 1, @@ -471,7 +472,6 @@ public: LAST_MODE = 3, }; -private: AudioStream *mChain; int mEventQueue; volatile int mDtmfEvent; @@ -948,16 +948,10 @@ void remove(JNIEnv *env, jobject thiz, jint socket) void setMode(JNIEnv *env, jobject thiz, jint mode) { - if (mode < 0 || mode > AudioGroup::LAST_MODE) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; - } AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); if (group && !group->setMode(mode)) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; } - env->SetIntField(thiz, gMode, mode); } void sendDtmf(JNIEnv *env, jobject thiz, jint event) @@ -969,10 +963,10 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event) } JNINativeMethod gMethods[] = { - {"add", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add}, - {"remove", "(I)V", (void *)remove}, - {"setMode", "(I)V", (void *)setMode}, - {"sendDtmf", "(I)V", (void *)sendDtmf}, + {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add}, + {"nativeRemove", "(I)V", (void *)remove}, + {"nativeSetMode", "(I)V", (void *)setMode}, + {"nativeSendDtmf", "(I)V", (void *)sendDtmf}, }; } // namespace -- cgit v1.2.3 From d47a47a131a8e7f80b934aec4cc7a10fd9fe1094 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 25 Oct 2010 17:04:36 +0800 Subject: Fix race between ending and answering a SIP call. + Also fix race between ending and changing (holding/unholding) a SIP call. + Remove an unused method. Bug : 3128233 Change-Id: Ie18d8333a88f0d9906d54988243d909b58e07e4b --- java/com/android/server/sip/SipSessionGroup.java | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 29f5f5a..30ddfb5 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -527,11 +527,14 @@ class SipSessionGroup implements SipListener { } public void answerCall(String sessionDescription, int timeout) { - try { - processCommand(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } catch (SipException e) { - onError(e); + synchronized (SipSessionGroup.this) { + if (mPeerProfile == null) return; + try { + processCommand(new MakeCallCommand(mPeerProfile, + sessionDescription, timeout)); + } catch (SipException e) { + onError(e); + } } } @@ -540,14 +543,11 @@ class SipSessionGroup implements SipListener { } public void changeCall(String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); - } - - public void changeCallWithTimeout( - String sessionDescription, int timeout) { - doCommandAsync(new MakeCallCommand(mPeerProfile, sessionDescription, - timeout)); + synchronized (SipSessionGroup.this) { + if (mPeerProfile == null) return; + doCommandAsync(new MakeCallCommand(mPeerProfile, + sessionDescription, timeout)); + } } public void register(int duration) { -- cgit v1.2.3 From 2e45c0b8f84172a004df4e3a1f50dbf1235474eb Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Wed, 3 Nov 2010 13:11:53 +0800 Subject: Fix SIP bug of different transport/port used for requests. bug: http://b/3156148 Change-Id: I4fa5b274d2e90ebde12d9e99822dc193a65bad32 --- java/android/net/sip/SipProfile.java | 50 +++++++++++++++++++++++------- java/com/android/server/sip/SipHelper.java | 5 +-- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 6977e30..4029ed0 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -20,6 +20,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import java.io.ObjectStreamException; import java.io.Serializable; import java.text.ParseException; import javax.sip.InvalidArgumentException; @@ -40,12 +41,15 @@ import javax.sip.address.URI; public class SipProfile implements Parcelable, Serializable, Cloneable { private static final long serialVersionUID = 1L; private static final int DEFAULT_PORT = 5060; + private static final String TCP = "TCP"; + private static final String UDP = "UDP"; private Address mAddress; private String mProxyAddress; private String mPassword; private String mDomain; - private String mProtocol = ListeningPoint.UDP; + private String mProtocol = UDP; private String mProfileName; + private int mPort = DEFAULT_PORT; private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; private transient int mCallingUid = 0; @@ -95,6 +99,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mUri.setUserPassword(profile.getPassword()); mDisplayName = profile.getDisplayName(); mProxyAddress = profile.getProxyAddress(); + mProfile.mPort = profile.getPort(); } /** @@ -171,12 +176,11 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * @throws IllegalArgumentException if the port number is out of range */ public Builder setPort(int port) throws IllegalArgumentException { - try { - mUri.setPort(port); - return this; - } catch (InvalidArgumentException e) { - throw new IllegalArgumentException(e); + if ((port > 65535) || (port < 1000)) { + throw new IllegalArgumentException("incorrect port arugment"); } + mProfile.mPort = port; + return this; } /** @@ -193,7 +197,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { throw new NullPointerException("protocol cannot be null"); } protocol = protocol.toUpperCase(); - if (!protocol.equals("UDP") && !protocol.equals("TCP")) { + if (!protocol.equals(UDP) && !protocol.equals(TCP)) { throw new IllegalArgumentException( "unsupported protocol: " + protocol); } @@ -258,13 +262,22 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mProfile.mPassword = mUri.getUserPassword(); mUri.setUserPassword(null); try { - mProfile.mAddress = mAddressFactory.createAddress( - mDisplayName, mUri); if (!TextUtils.isEmpty(mProxyAddress)) { SipURI uri = (SipURI) mAddressFactory.createURI(fix(mProxyAddress)); mProfile.mProxyAddress = uri.getHost(); + } else { + if (!mProfile.mProtocol.equals(UDP)) { + mUri.setTransportParam(mProfile.mProtocol); + } + if (mProfile.mPort != DEFAULT_PORT) { + mUri.setPort(mProfile.mPort); + } } + mProfile.mAddress = mAddressFactory.createAddress( + mDisplayName, mUri); + } catch (InvalidArgumentException e) { + throw new RuntimeException(e); } catch (ParseException e) { // must not occur throw new RuntimeException(e); @@ -286,6 +299,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mSendKeepAlive = (in.readInt() == 0) ? false : true; mAutoRegistration = (in.readInt() == 0) ? false : true; mCallingUid = in.readInt(); + mPort = in.readInt(); } @Override @@ -299,6 +313,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeInt(mSendKeepAlive ? 1 : 0); out.writeInt(mAutoRegistration ? 1 : 0); out.writeInt(mCallingUid); + out.writeInt(mPort); } @Override @@ -322,7 +337,13 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * @return the SIP URI string of this profile */ public String getUriString() { - return mAddress.getURI().toString(); + // We need to return the sip uri domain instead of + // the SIP URI with transport, port information if + // the outbound proxy address exists. + if (!TextUtils.isEmpty(mProxyAddress)) { + return "sip:" + getUserName() + "@" + mDomain; + } + return getUri().toString(); } /** @@ -377,8 +398,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { * @return the port number of the SIP server */ public int getPort() { - int port = getUri().getPort(); - return (port == -1) ? DEFAULT_PORT : port; + return mPort; } /** @@ -441,4 +461,10 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { public int getCallingUid() { return mCallingUid; } + + private Object readResolve() throws ObjectStreamException { + // For compatibility. + if (mPort == 0) mPort = DEFAULT_PORT; + return this; + } } diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 13e6f14..518543a 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -215,8 +215,9 @@ class SipHelper { String tag) throws ParseException, SipException { FromHeader fromHeader = createFromHeader(userProfile, tag); ToHeader toHeader = createToHeader(userProfile); - SipURI requestURI = mAddressFactory.createSipURI("sip:" - + userProfile.getSipDomain()); + SipURI requestURI = mAddressFactory.createSipURI( + userProfile.getUriString().replaceFirst( + userProfile.getUserName() + "@", "")); List viaHeaders = createViaHeaders(); CallIdHeader callIdHeader = createCallIdHeader(); CSeqHeader cSeqHeader = createCSeqHeader(requestType); -- cgit v1.2.3 From 90189114df094fb8170c26f8385ff956175ab58c Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Dec 2010 16:13:29 +0800 Subject: Check port in create peer's SIP profile. SipURI returns port -1 when port is not present in the URI. Don't call SipProfile.Builder.setPort() when that happens. Change-Id: Ic5fe7301195705a77010038cae20d6629b33135e --- java/android/net/sip/SipProfile.java | 2 +- java/com/android/server/sip/SipSessionGroup.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 4029ed0..f8fd2b7 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -177,7 +177,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { */ public Builder setPort(int port) throws IllegalArgumentException { if ((port > 65535) || (port < 1000)) { - throw new IllegalArgumentException("incorrect port arugment"); + throw new IllegalArgumentException("incorrect port arugment: " + port); } mProfile.mPort = port; return this; diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 30ddfb5..edf8b52 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1333,10 +1333,12 @@ class SipSessionGroup implements SipListener { SipURI uri = (SipURI) address.getURI(); String username = uri.getUser(); if (username == null) username = ANONYMOUS; - return new SipProfile.Builder(username, uri.getHost()) - .setPort(uri.getPort()) - .setDisplayName(address.getDisplayName()) - .build(); + int port = uri.getPort(); + SipProfile.Builder builder = + new SipProfile.Builder(username, uri.getHost()) + .setDisplayName(address.getDisplayName()); + if (port > 0) builder.setPort(port); + return builder.build(); } catch (IllegalArgumentException e) { throw new SipException("createPeerProfile()", e); } catch (ParseException e) { -- cgit v1.2.3 From 007493a9f98959c350c3787230df9afa346ee938 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Dec 2010 20:46:50 +0800 Subject: Check port in create peer's SIP profile. SipURI returns port -1 when port is not present in the URI. Don't call SipProfile.Builder.setPort() when that happens. Bug: 3291248 Change-Id: I8e608cbc56ea82862df55fdba885f6a864db83ab --- java/android/net/sip/SipProfile.java | 2 +- java/com/android/server/sip/SipSessionGroup.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 4029ed0..f8fd2b7 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -177,7 +177,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { */ public Builder setPort(int port) throws IllegalArgumentException { if ((port > 65535) || (port < 1000)) { - throw new IllegalArgumentException("incorrect port arugment"); + throw new IllegalArgumentException("incorrect port arugment: " + port); } mProfile.mPort = port; return this; diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 30ddfb5..edf8b52 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1333,10 +1333,12 @@ class SipSessionGroup implements SipListener { SipURI uri = (SipURI) address.getURI(); String username = uri.getUser(); if (username == null) username = ANONYMOUS; - return new SipProfile.Builder(username, uri.getHost()) - .setPort(uri.getPort()) - .setDisplayName(address.getDisplayName()) - .build(); + int port = uri.getPort(); + SipProfile.Builder builder = + new SipProfile.Builder(username, uri.getHost()) + .setDisplayName(address.getDisplayName()); + if (port > 0) builder.setPort(port); + return builder.build(); } catch (IllegalArgumentException e) { throw new SipException("createPeerProfile()", e); } catch (ParseException e) { -- cgit v1.2.3 From a0b7374a8afc7fb1db2eebb32ecba0f10db083cd Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 16 Dec 2010 11:50:45 +0800 Subject: Remove SIP realm/domain check as the realm may be different from the domain. Bug: 3283834 Change-Id: I64c9f0d6d626afdb397c5d378d30afa9d6a64ca9 --- java/com/android/server/sip/SipSessionGroup.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index edf8b52..3275317 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -872,8 +872,13 @@ class SipSessionGroup implements SipListener { } return true; } else { - onError(SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); + if (crossDomainAuthenticationRequired(response)) { + onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, + getRealmFromResponse(response)); + } else { + onError(SipErrorCode.INVALID_CREDENTIALS, + "incorrect username or password"); + } return false; } } @@ -1025,10 +1030,7 @@ class SipSessionGroup implements SipListener { return true; case Response.UNAUTHORIZED: case Response.PROXY_AUTHENTICATION_REQUIRED: - if (crossDomainAuthenticationRequired(response)) { - onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, - getRealmFromResponse(response)); - } else if (handleAuthentication(event)) { + if (handleAuthentication(event)) { addSipSession(this); } return true; -- cgit v1.2.3 From 38ee75e8c0722118d26a23afe5b509b6a09302f4 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 20 Dec 2010 19:08:24 +0800 Subject: Check if VoIP API is supported in SipManager. This is to make SipManager.isVoipSupported() effective. Also add NPE check now that we may return null SipAudioCall when VOIP is not supported. Bug: 3251016 Change-Id: Icd551123499f55eef190743b90980922893c4a13 --- java/android/net/sip/SipAudioCall.java | 20 ++++++++++++++++++-- java/android/net/sip/SipManager.java | 16 +++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 286f60b..20ffd1b 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -519,10 +519,15 @@ public class SipAudioCall { * @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 + * the session or VOIP API is not supported by the device + * @see SipManager#isVoipSupported */ public void attachCall(SipSession session, String sessionDescription) throws SipException { + if (!SipManager.isVoipSupported(mContext)) { + throw new SipException("VOIP API is not supported"); + } + synchronized (this) { mSipSession = session; mPeerSd = sessionDescription; @@ -548,10 +553,15 @@ public class SipAudioCall { * 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 + * call or VOIP API is not supported by the device + * @see SipManager#isVoipSupported */ public void makeCall(SipProfile peerProfile, SipSession sipSession, int timeout) throws SipException { + if (!SipManager.isVoipSupported(mContext)) { + throw new SipException("VOIP API is not supported"); + } + synchronized (this) { mSipSession = sipSession; try { @@ -595,6 +605,9 @@ public class SipAudioCall { public void holdCall(int timeout) throws SipException { synchronized (this) { if (mHold) return; + if (mSipSession == null) { + throw new SipException("Not in a call to hold call"); + } mSipSession.changeCall(createHoldOffer().encode(), timeout); mHold = true; setAudioGroupMode(); @@ -614,6 +627,9 @@ public class SipAudioCall { */ public void answerCall(int timeout) throws SipException { synchronized (this) { + if (mSipSession == null) { + throw new SipException("No call to answer"); + } try { mAudioStream = new AudioStream(InetAddress.getByName( getLocalIp())); diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 2e38662..dce46fe 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -133,7 +133,7 @@ public class SipManager { } /** - * Returns true if the system supports SIP-based VoIP. + * Returns true if the system supports SIP-based VOIP API. */ public static boolean isVoipSupported(Context context) { return context.getPackageManager().hasSystemFeature( @@ -305,12 +305,17 @@ public class SipManager { * @param timeout the timeout value in seconds. Default value (defined by * SIP protocol) is used if {@code timeout} is zero or negative. * @return a {@link SipAudioCall} object - * @throws SipException if calling the SIP service results in an error + * @throws SipException if calling the SIP service results in an error or + * VOIP API is not supported by the device * @see SipAudioCall.Listener#onError + * @see #isVoipSupported */ public SipAudioCall makeAudioCall(SipProfile localProfile, SipProfile peerProfile, SipAudioCall.Listener listener, int timeout) throws SipException { + if (!isVoipSupported(mContext)) { + throw new SipException("VOIP API is not supported"); + } SipAudioCall call = new SipAudioCall(mContext, localProfile); call.setListener(listener); SipSession s = createSipSession(localProfile, null); @@ -332,12 +337,17 @@ public class SipManager { * @param timeout the timeout value in seconds. Default value (defined by * SIP protocol) is used if {@code timeout} is zero or negative. * @return a {@link SipAudioCall} object - * @throws SipException if calling the SIP service results in an error + * @throws SipException if calling the SIP service results in an error or + * VOIP API is not supported by the device * @see SipAudioCall.Listener#onError + * @see #isVoipSupported */ public SipAudioCall makeAudioCall(String localProfileUri, String peerProfileUri, SipAudioCall.Listener listener, int timeout) throws SipException { + if (!isVoipSupported(mContext)) { + throw new SipException("VOIP API is not supported"); + } try { return makeAudioCall( new SipProfile.Builder(localProfileUri).build(), -- cgit v1.2.3 From 9795c258778208fd96a072b7a91028285aafbc32 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 4 Jan 2011 19:10:06 +0800 Subject: RTP: Send silence packets on idle streams for every second. Originally a stream does not send packets when it is receive-only or there is nothing to mix. However, this causes some problems with certain firewalls and proxies. A firewall might remove a port mapping when there is no outgoing packet for a preiod of time, and a proxy might wait for incoming packets from both sides before start forwarding. To solve these problems, we send out a silence packet on the stream for every second. It should be good enough to keep the stream alive with relatively low resources. Bug: 3119690 Change-Id: Ib9c55e5dddfba28928bd9b376832b68bda24c0e4 --- jni/rtp/AudioGroup.cpp | 80 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 0c8a725..60abf2a 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -63,6 +63,14 @@ int gRandom = -1; // real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers // are chosen by experiments and each of them can be adjusted as needed. +// Originally a stream does not send packets when it is receive-only or there is +// nothing to mix. However, this causes some problems with certain firewalls and +// proxies. A firewall might remove a port mapping when there is no outgoing +// packet for a preiod of time, and a proxy might wait for incoming packets from +// both sides before start forwarding. To solve these problems, we send out a +// silence packet on the stream for every second. It should be good enough to +// keep the stream alive with relatively low resources. + // Other notes: // + We use elapsedRealtime() to get the time. Since we use 32bit variables // instead of 64bit ones, comparison must be done by subtraction. @@ -110,7 +118,7 @@ private: int mSampleRate; int mSampleCount; int mInterval; - int mLogThrottle; + int mKeepAlive; int16_t *mBuffer; int mBufferMask; @@ -262,12 +270,8 @@ void AudioStream::encode(int tick, AudioStream *chain) ++mSequence; mTimestamp += mSampleCount; - if (mMode == RECEIVE_ONLY) { - return; - } - // If there is an ongoing DTMF event, send it now. - if (mDtmfEvent != -1) { + if (mMode != RECEIVE_ONLY && mDtmfEvent != -1) { int duration = mTimestamp - mDtmfStart; // Make sure duration is reasonable. if (duration >= 0 && duration < mSampleRate * 100) { @@ -289,43 +293,55 @@ void AudioStream::encode(int tick, AudioStream *chain) mDtmfEvent = -1; } - // It is time to mix streams. - bool mixed = false; int32_t buffer[mSampleCount + 3]; - memset(buffer, 0, sizeof(buffer)); - while (chain) { - if (chain != this && - chain->mix(buffer, tick - mInterval, tick, mSampleRate)) { - mixed = true; + int16_t samples[mSampleCount]; + if (mMode == RECEIVE_ONLY) { + if ((mTick ^ mKeepAlive) >> 10 == 0) { + return; } - chain = chain->mNext; - } - if (!mixed) { - if ((mTick ^ mLogThrottle) >> 10) { - mLogThrottle = mTick; - LOGV("stream[%d] no data", mSocket); + mKeepAlive = mTick; + memset(samples, 0, sizeof(samples)); + } else { + // Mix all other streams. + bool mixed = false; + memset(buffer, 0, sizeof(buffer)); + while (chain) { + if (chain != this && + chain->mix(buffer, tick - mInterval, tick, mSampleRate)) { + mixed = true; + } + chain = chain->mNext; } - return; - } - // Cook the packet and send it out. - int16_t samples[mSampleCount]; - for (int i = 0; i < mSampleCount; ++i) { - int32_t sample = buffer[i]; - if (sample < -32768) { - sample = -32768; - } - if (sample > 32767) { - sample = 32767; + if (mixed) { + // Saturate into 16 bits. + for (int i = 0; i < mSampleCount; ++i) { + int32_t sample = buffer[i]; + if (sample < -32768) { + sample = -32768; + } + if (sample > 32767) { + sample = 32767; + } + samples[i] = sample; + } + } else { + if ((mTick ^ mKeepAlive) >> 10 == 0) { + return; + } + mKeepAlive = mTick; + memset(samples, 0, sizeof(samples)); + LOGV("stream[%d] no data", mSocket); } - samples[i] = sample; } + if (!mCodec) { // Special case for device stream. send(mSocket, samples, sizeof(samples), MSG_DONTWAIT); return; } + // Cook the packet and send it out. buffer[0] = htonl(mCodecMagic | mSequence); buffer[1] = htonl(mTimestamp); buffer[2] = mSsrc; @@ -883,7 +899,7 @@ void add(JNIEnv *env, jobject thiz, jint mode, int codecType = -1; char codecName[16]; int sampleRate = -1; - sscanf(codecSpec, "%d %[^/]%*c%d", &codecType, codecName, &sampleRate); + sscanf(codecSpec, "%d %15[^/]%*c%d", &codecType, codecName, &sampleRate); codec = newAudioCodec(codecName); int sampleCount = (codec ? codec->set(sampleRate, codecSpec) : -1); env->ReleaseStringUTFChars(jCodecSpec, codecSpec); -- cgit v1.2.3 From 73072b33923ace309a8faf946f69b7703401b085 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 6 Jan 2011 13:03:30 +0800 Subject: SipService: release wake lock for cancelled tasks. Bug: 3327004 Change-Id: I0691cd70edf61f815ecb0613aca85babd89f6cc4 --- java/com/android/server/sip/SipService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 3af6e78..8945d1e 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -1055,7 +1055,10 @@ public final class SipService extends ISipService.Stub { // we want to skip the interim ones) but deliver bad news // immediately if (connected) { - if (mTask != null) mTask.cancel(); + if (mTask != null) { + mTask.cancel(); + mMyWakeLock.release(mTask); + } mTask = new MyTimerTask(type, connected); mTimer.schedule(mTask, 2 * 1000L); // hold wakup lock so that we can finish changes before the @@ -1096,6 +1099,7 @@ public final class SipService extends ISipService.Stub { if (mTask != this) { Log.w(TAG, " unexpected task: " + mNetworkType + (mConnected ? " CONNECTED" : "DISCONNECTED")); + mMyWakeLock.release(this); return; } mTask = null; -- cgit v1.2.3 From 448727b9f6fd1f7030b120f6315d418692cab0f7 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 6 Jan 2011 15:51:45 +0800 Subject: Do not set back to AudioManager.MODE_NORMAL in SipAudioCall. Change-Id: I8f68e01e5f8c73bb8afd44312cbfadb55aab4330 --- java/android/net/sip/SipAudioCall.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index ce18ec5..90d286d 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -999,16 +999,6 @@ public class SipAudioCall { 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) { -- cgit v1.2.3 From 04fcfe3ce9fdc5d3487c9760092649afc2de2dc8 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 6 Jan 2011 17:43:24 +0800 Subject: Enable built-in echo canceler if available. 1. Always initialize AudioRecord with VOICE_COMMUNICATION. 2. If echo canceler is available, disable our echo suppressor. Change-Id: Idf18d3833189a8478c1b252ebe6ce55e923280b3 --- jni/rtp/AudioGroup.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 64db250..c031eee 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -484,7 +484,7 @@ private: ON_HOLD = 0, MUTED = 1, NORMAL = 2, - EC_ENABLED = 3, + ECHO_SUPPRESSION = 3, LAST_MODE = 3, }; @@ -619,6 +619,10 @@ bool AudioGroup::setMode(int mode) if (mode < 0 || mode > LAST_MODE) { return false; } + if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters( + 0, String8("ec_supported")) == "ec_supported=yes") { + mode = NORMAL; + } if (mMode == mode) { return true; } @@ -775,8 +779,8 @@ bool AudioGroup::DeviceThread::threadLoop() AudioTrack track; AudioRecord record; if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || - record.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( + AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AudioSystem::PCM_16_BIT, AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { LOGE("cannot initialize audio device"); return false; -- cgit v1.2.3 From 5e90e3f24fb5b1e19e31b07f4c6602ad263f4e52 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 7 Jan 2011 11:57:22 +0800 Subject: SipService: registers broadcast receivers on demand. The previous implementation registers receivers when SipService starts up. If the user doesn't use SIP at all, SipService will still process connecivity and wifi state change events, which involves holding wake lock and thus consumes power unnecessarily. With this CL, SipService is completely idle if the user doesn't use SIP at all. It registers receivers only when at least one account is opened. Bug: 3326998 Change-Id: Ib70e0cf2c808e0ebab4c3c43dcab5532d24e5eeb --- java/com/android/server/sip/SipService.java | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 8945d1e..dc66989 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -105,7 +105,7 @@ public final class SipService extends ISipService.Stub { if (SipManager.isApiSupported(context)) { ServiceManager.addService("sip", new SipService(context)); context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); - if (DEBUG) Log.i(TAG, "SIP service started"); + if (DEBUG) Log.d(TAG, "SIP service started"); } } @@ -113,10 +113,6 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " service started!"); mContext = context; mConnectivityReceiver = new ConnectivityReceiver(); - context.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - context.registerReceiver(mWifiStateReceiver, - new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); mMyWakeLock = new SipWakeLock((PowerManager) context.getSystemService(Context.POWER_SERVICE)); @@ -124,7 +120,7 @@ public final class SipService extends ISipService.Stub { mWifiOnly = SipManager.isSipWifiOnly(context); } - BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -147,6 +143,20 @@ public final class SipService extends ISipService.Stub { } }; + private void registerReceivers() { + mContext.registerReceiver(mConnectivityReceiver, + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + mContext.registerReceiver(mWifiStateReceiver, + new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + if (DEBUG) Log.d(TAG, " +++ register receivers"); + } + + private void unregisterReceivers() { + mContext.unregisterReceiver(mConnectivityReceiver); + mContext.unregisterReceiver(mWifiStateReceiver); + if (DEBUG) Log.d(TAG, " --- unregister receivers"); + } + private MyExecutor getExecutor() { // create mExecutor lazily if (mExecutor == null) mExecutor = new MyExecutor(); @@ -166,12 +176,14 @@ public final class SipService extends ISipService.Stub { return profiles.toArray(new SipProfile[profiles.size()]); } - public void open(SipProfile localProfile) { + public synchronized void open(SipProfile localProfile) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); try { + boolean addingFirstProfile = mSipGroups.isEmpty(); createGroup(localProfile); + if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); } catch (SipException e) { Log.e(TAG, "openToMakeCalls()", e); // TODO: how to send the exception back @@ -192,8 +204,10 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + incomingCallPendingIntent + ": " + listener); try { + boolean addingFirstProfile = mSipGroups.isEmpty(); SipSessionGroupExt group = createGroup(localProfile, incomingCallPendingIntent, listener); + if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); if (mWifiEnabled) grabWifiLock(); @@ -235,6 +249,7 @@ public final class SipService extends ISipService.Stub { releaseWifiLock(); mMyWakeLock.reset(); // in case there's leak } + if (mSipGroups.isEmpty()) unregisterReceivers(); } public synchronized boolean isOpened(String localProfileUri) { -- cgit v1.2.3 From 2d6c1e2628fd8e090cc9fe6c59141ae5caf117bd Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 6 Jan 2011 18:44:43 +0800 Subject: Add auth. username in SipProfile. bug:3326867 Change-Id: I2a62c75fb3f5e9c6ec2e00b29396e93b0c183d9b --- java/android/net/sip/SipProfile.java | 26 ++++++++++++++++++++++++ java/com/android/server/sip/SipSessionGroup.java | 4 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index f8fd2b7..c7e18df 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -49,6 +49,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { private String mDomain; private String mProtocol = UDP; private String mProfileName; + private String mAuthUserName; private int mPort = DEFAULT_PORT; private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; @@ -146,6 +147,18 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { : "sip:" + uriString); } + /** + * Sets the username used for authentication. + * + * @param name auth. name of the profile + * @return this builder object + * @hide // TODO: remove when we make it public + */ + public Builder setAuthUserName(String name) { + mProfile.mAuthUserName = name; + return this; + } + /** * Sets the name of the profile. This name is given by user. * @@ -300,6 +313,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mAutoRegistration = (in.readInt() == 0) ? false : true; mCallingUid = in.readInt(); mPort = in.readInt(); + mAuthUserName = in.readString(); } @Override @@ -314,6 +328,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeInt(mAutoRegistration ? 1 : 0); out.writeInt(mCallingUid); out.writeInt(mPort); + out.writeString(mAuthUserName); } @Override @@ -374,6 +389,17 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { return getUri().getUser(); } + /** + * Gets the username for authentication. If it is null, then the username + * should be used in authentication instead. + * + * @return the auth. username + * @hide // TODO: remove when we make it public + */ + public String getAuthUserName() { + return mAuthUserName; + } + /** * Gets the password. * diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 3275317..aa616a9 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -895,7 +895,9 @@ class SipSessionGroup implements SipListener { challengedTransaction, String realm) { return new UserCredentials() { public String getUserName() { - return mLocalProfile.getUserName(); + String username = mLocalProfile.getAuthUserName(); + return (!TextUtils.isEmpty(username) ? username : + mLocalProfile.getUserName()); } public String getPassword() { -- cgit v1.2.3 From 1b666e24bacbe3695ca5fe2f996c177c3f10d5f0 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 6 Jan 2011 18:44:43 +0800 Subject: Merge "Add auth. username in SipProfile." from gingerbread. bug:3326867 Change-Id: Ic67dd7d4858f28224e4f01ad8b65bcd3a3c15f10 --- java/android/net/sip/SipProfile.java | 26 ++++++++++++++++++++++++ java/com/android/server/sip/SipSessionGroup.java | 4 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index f8fd2b7..c7e18df 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -49,6 +49,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { private String mDomain; private String mProtocol = UDP; private String mProfileName; + private String mAuthUserName; private int mPort = DEFAULT_PORT; private boolean mSendKeepAlive = false; private boolean mAutoRegistration = true; @@ -146,6 +147,18 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { : "sip:" + uriString); } + /** + * Sets the username used for authentication. + * + * @param name auth. name of the profile + * @return this builder object + * @hide // TODO: remove when we make it public + */ + public Builder setAuthUserName(String name) { + mProfile.mAuthUserName = name; + return this; + } + /** * Sets the name of the profile. This name is given by user. * @@ -300,6 +313,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { mAutoRegistration = (in.readInt() == 0) ? false : true; mCallingUid = in.readInt(); mPort = in.readInt(); + mAuthUserName = in.readString(); } @Override @@ -314,6 +328,7 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { out.writeInt(mAutoRegistration ? 1 : 0); out.writeInt(mCallingUid); out.writeInt(mPort); + out.writeString(mAuthUserName); } @Override @@ -374,6 +389,17 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { return getUri().getUser(); } + /** + * Gets the username for authentication. If it is null, then the username + * should be used in authentication instead. + * + * @return the auth. username + * @hide // TODO: remove when we make it public + */ + public String getAuthUserName() { + return mAuthUserName; + } + /** * Gets the password. * diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 3275317..aa616a9 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -895,7 +895,9 @@ class SipSessionGroup implements SipListener { challengedTransaction, String realm) { return new UserCredentials() { public String getUserName() { - return mLocalProfile.getUserName(); + String username = mLocalProfile.getAuthUserName(); + return (!TextUtils.isEmpty(username) ? username : + mLocalProfile.getUserName()); } public String getPassword() { -- cgit v1.2.3 From f4a38e8e2f83402d2307edf67ef79b6f74990c8b Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 6 Jan 2011 13:03:30 +0800 Subject: Merge "SipService: release wake lock for cancelled tasks." Bug: 3327004 Change-Id: Ice47f973b5f2969f26eaa83a3e4795b2e153ba8b --- java/com/android/server/sip/SipService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 3af6e78..8945d1e 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -1055,7 +1055,10 @@ public final class SipService extends ISipService.Stub { // we want to skip the interim ones) but deliver bad news // immediately if (connected) { - if (mTask != null) mTask.cancel(); + if (mTask != null) { + mTask.cancel(); + mMyWakeLock.release(mTask); + } mTask = new MyTimerTask(type, connected); mTimer.schedule(mTask, 2 * 1000L); // hold wakup lock so that we can finish changes before the @@ -1096,6 +1099,7 @@ public final class SipService extends ISipService.Stub { if (mTask != this) { Log.w(TAG, " unexpected task: " + mNetworkType + (mConnected ? " CONNECTED" : "DISCONNECTED")); + mMyWakeLock.release(this); return; } mTask = null; -- cgit v1.2.3 From dc7545495e1ef8bad32cd78775f4dc072015eea0 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 7 Jan 2011 11:57:22 +0800 Subject: Merge "SipService: registers broadcast receivers on demand." The previous implementation registers receivers when SipService starts up. If the user doesn't use SIP at all, SipService will still process connecivity and wifi state change events, which involves holding wake lock and thus consumes power unnecessarily. With this CL, SipService is completely idle if the user doesn't use SIP at all. It registers receivers only when at least one account is opened. Bug: 3326998 Change-Id: Idea43747f8204b0ccad3fc05a1b1c0b29c9b2557 --- java/com/android/server/sip/SipService.java | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 3af6e78..afdc2c2 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -105,7 +105,7 @@ public final class SipService extends ISipService.Stub { if (SipManager.isApiSupported(context)) { ServiceManager.addService("sip", new SipService(context)); context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); - if (DEBUG) Log.i(TAG, "SIP service started"); + if (DEBUG) Log.d(TAG, "SIP service started"); } } @@ -113,10 +113,6 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " service started!"); mContext = context; mConnectivityReceiver = new ConnectivityReceiver(); - context.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - context.registerReceiver(mWifiStateReceiver, - new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); mMyWakeLock = new SipWakeLock((PowerManager) context.getSystemService(Context.POWER_SERVICE)); @@ -124,7 +120,7 @@ public final class SipService extends ISipService.Stub { mWifiOnly = SipManager.isSipWifiOnly(context); } - BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -147,6 +143,20 @@ public final class SipService extends ISipService.Stub { } }; + private void registerReceivers() { + mContext.registerReceiver(mConnectivityReceiver, + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + mContext.registerReceiver(mWifiStateReceiver, + new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + if (DEBUG) Log.d(TAG, " +++ register receivers"); + } + + private void unregisterReceivers() { + mContext.unregisterReceiver(mConnectivityReceiver); + mContext.unregisterReceiver(mWifiStateReceiver); + if (DEBUG) Log.d(TAG, " --- unregister receivers"); + } + private MyExecutor getExecutor() { // create mExecutor lazily if (mExecutor == null) mExecutor = new MyExecutor(); @@ -166,12 +176,14 @@ public final class SipService extends ISipService.Stub { return profiles.toArray(new SipProfile[profiles.size()]); } - public void open(SipProfile localProfile) { + public synchronized void open(SipProfile localProfile) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); try { + boolean addingFirstProfile = mSipGroups.isEmpty(); createGroup(localProfile); + if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); } catch (SipException e) { Log.e(TAG, "openToMakeCalls()", e); // TODO: how to send the exception back @@ -192,8 +204,10 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + incomingCallPendingIntent + ": " + listener); try { + boolean addingFirstProfile = mSipGroups.isEmpty(); SipSessionGroupExt group = createGroup(localProfile, incomingCallPendingIntent, listener); + if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); if (mWifiEnabled) grabWifiLock(); @@ -235,6 +249,7 @@ public final class SipService extends ISipService.Stub { releaseWifiLock(); mMyWakeLock.reset(); // in case there's leak } + if (mSipGroups.isEmpty()) unregisterReceivers(); } public synchronized boolean isOpened(String localProfileUri) { -- cgit v1.2.3 From 9252644a65c951387b2ccd8817cfa32a18b765b0 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Mon, 15 Nov 2010 12:11:32 -0800 Subject: do not merge bug 3370834 Cherrypick from master Cherripick from master CL 79833, 79417, 78864, 80332, 87500 Add new audio mode and recording source for audio communications other than telelphony. The audio mode MODE_IN_CALL signals the system the device a phone call is currently underway. There was no way for audio video chat or VoIP applications to signal a call is underway, but not using the telephony resources. This change introduces a new mode to address this. Changes in other parts of the system (java and native) are required to take this new mode into account. The generic AudioPolicyManager is updated to not use its phone state variable directly, but to use two new convenience methods, isInCall() and isStateInCall(int) instead. Add a recording source used to designate a recording stream for voice communications such as VoIP. Update the platform-independent audio policy manager to pass the nature of the audio recording source to the audio policy client interface through the AudioPolicyClientInterface::setParameters() method. SIP calls should set the audio mode to MODE_IN_COMMUNICATION, Audio mode MODE_IN_CALL is reserved for telephony. SIP: Enable built-in echo canceler if available. 1. Always initialize AudioRecord with VOICE_COMMUNICATION. 2. If echo canceler is available, disable our echo suppressor. Note that this CL is intentionally not correcting the getAudioSourceMax() return value in MediaRecorder.java as the new source is hidden here. Change-Id: Ie68cd03c50553101aa2ad838fe9459b2cf151bc8 --- jni/rtp/AudioGroup.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 60abf2a..2cbd023 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -483,7 +483,7 @@ public: ON_HOLD = 0, MUTED = 1, NORMAL = 2, - EC_ENABLED = 3, + ECHO_SUPPRESSION = 3, LAST_MODE = 3, }; @@ -619,6 +619,10 @@ bool AudioGroup::setMode(int mode) if (mode < 0 || mode > LAST_MODE) { return false; } + if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters( + 0, String8("ec_supported")) == "ec_supported=yes") { + mode = NORMAL; + } if (mMode == mode) { return true; } @@ -775,8 +779,8 @@ bool AudioGroup::DeviceThread::threadLoop() AudioTrack track; AudioRecord record; if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || - record.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( + AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AudioSystem::PCM_16_BIT, AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { LOGE("cannot initialize audio device"); return false; -- cgit v1.2.3 From 1c1df003bcf63b473d1ed90bd771963381e1be93 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 10 Feb 2011 15:20:41 +0800 Subject: Make SIP AuthName APIs public. bug:3326867 Change-Id: I766e6e28f6ad3e84de2c9e24850d472ad00271cc --- java/android/net/sip/SipProfile.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index c7e18df..34d91dd 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -150,9 +150,8 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { /** * Sets the username used for authentication. * - * @param name auth. name of the profile + * @param name authentication username of the profile * @return this builder object - * @hide // TODO: remove when we make it public */ public Builder setAuthUserName(String name) { mProfile.mAuthUserName = name; @@ -391,10 +390,10 @@ public class SipProfile implements Parcelable, Serializable, Cloneable { /** * Gets the username for authentication. If it is null, then the username - * should be used in authentication instead. + * is used in authentication instead. * - * @return the auth. username - * @hide // TODO: remove when we make it public + * @return the authentication username + * @see #getUserName */ public String getAuthUserName() { return mAuthUserName; -- cgit v1.2.3 From a6d2f402722e65536628b161f07534b5e886882a Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Mon, 21 Feb 2011 11:50:25 +0800 Subject: Add rport argument for a reinvite request. bug:3461707 Change-Id: I69a4f84dde3929c754c838fd12e624b774f44826 --- java/com/android/server/sip/SipHelper.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 518543a..ac580e7 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -284,6 +284,13 @@ class SipHelper { mHeaderFactory.createContentTypeHeader( "application", "sdp")); + // Adding rport argument in the request could fix some SIP servers + // in resolving the initiator's NAT port mapping for relaying the + // response message from the other end. + + ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + if (viaHeader != null) viaHeader.setRPort(); + ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request); -- cgit v1.2.3 From 2c615c8f6ce8939132e4597cfacdb933b718426c Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Fri, 25 Feb 2011 10:17:16 +0800 Subject: Activate the wifi high perf. for sip calls. bug:3487791 Change-Id: I7d8d146f8542cd7df387547c7ce3d5ded27f8e97 --- java/android/net/sip/SipAudioCall.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 90d286d..b46f826 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -754,7 +754,6 @@ public class SipAudioCall { } private void grabWifiHighPerfLock() { - /* not available in master yet if (mWifiHighPerfLock == null) { Log.v(TAG, "acquire wifi high perf lock"); mWifiHighPerfLock = ((WifiManager) @@ -762,7 +761,6 @@ public class SipAudioCall { .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); mWifiHighPerfLock.acquire(); } - */ } private void releaseWifiHighPerfLock() { -- cgit v1.2.3 From 0a3e1f1851b26657b865ae3c91200c59dfc411fe Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 3 Mar 2011 07:59:00 +0800 Subject: RTP: update javadocs. Change-Id: If600df0eb1e6135aed9f3b2eacfb6bc9ed5d78ff --- java/android/net/rtp/AudioGroup.java | 86 ++++++++++++++++++----------------- java/android/net/rtp/AudioStream.java | 4 +- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index a6b54d8..20c8969 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -16,41 +16,47 @@ package android.net.rtp; +import android.media.AudioManager; + import java.util.HashMap; import java.util.Map; /** - * An AudioGroup acts as a router connected to the speaker, the microphone, and - * {@link AudioStream}s. Its execution loop consists of four steps. First, for - * each AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its - * incoming packets and stores in its buffer. Then, if the microphone is - * enabled, processes the recorded audio and stores in its buffer. Third, if the - * speaker is enabled, mixes and playbacks buffers of all AudioStreams. Finally, - * for each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all - * other buffers and sends back the encoded packets. An AudioGroup does nothing - * if there is no AudioStream in it. + * An AudioGroup is an audio hub for the speaker, the microphone, and + * {@link AudioStream}s. Each of these components can be logically turned on + * or off by calling {@link #setMode(int)} or {@link RtpStream#setMode(int)}. + * The AudioGroup will go through these components and process them one by one + * within its execution loop. The loop consists of four steps. First, for each + * AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming + * packets and stores in its buffer. Then, if the microphone is enabled, + * processes the recorded audio and stores in its buffer. Third, if the speaker + * is enabled, mixes all AudioStream buffers and plays back. Finally, for each + * AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other + * buffers and sends back the encoded packets. An AudioGroup does nothing if + * there is no AudioStream in it. * *

    Few things must be noticed before using these classes. The performance is * highly related to the system load and the network bandwidth. Usually a * simpler {@link AudioCodec} costs fewer CPU cycles but requires more network - * bandwidth, and vise versa. Using two AudioStreams at the same time not only - * doubles the load but also the bandwidth. The condition varies from one device - * to another, and developers must choose the right combination in order to get - * the best result. + * bandwidth, and vise versa. Using two AudioStreams at the same time doubles + * not only the load but also the bandwidth. The condition varies from one + * device to another, and developers should choose the right combination in + * order to get the best result.

    * *

    It is sometimes useful to keep multiple AudioGroups at the same time. For * example, a Voice over IP (VoIP) application might want to put a conference * call on hold in order to make a new call but still allow people in the - * previous call to talk to each other. This can be done easily using two + * conference call talking to each other. This can be done easily using two * AudioGroups, but there are some limitations. Since the speaker and the - * microphone are shared globally, only one AudioGroup is allowed to run in - * modes other than {@link #MODE_ON_HOLD}. In addition, before adding an - * AudioStream into an AudioGroup, one should always put all other AudioGroups - * into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly - * initialized.

    + * microphone are globally shared resources, only one AudioGroup at a time is + * allowed to run in a mode other than {@link #MODE_ON_HOLD}. The others will + * be unable to acquire these resources and fail silently.

    * *

    Using this class requires - * {@link android.Manifest.permission#RECORD_AUDIO} permission.

    + * {@link android.Manifest.permission#RECORD_AUDIO} permission. Developers + * should set the audio mode to {@link AudioManager#MODE_IN_COMMUNICATION} + * using {@link AudioManager#setMode(int)} and change it back when none of + * the AudioGroups is in use.

    * * @see AudioStream * @hide @@ -58,13 +64,13 @@ import java.util.Map; public class AudioGroup { /** * This mode is similar to {@link #MODE_NORMAL} except the speaker and - * the microphone are disabled. + * the microphone are both disabled. */ public static final int MODE_ON_HOLD = 0; /** * This mode is similar to {@link #MODE_NORMAL} except the microphone is - * muted. + * disabled. */ public static final int MODE_MUTED = 1; @@ -137,20 +143,18 @@ public class AudioGroup { private native void nativeSetMode(int mode); // Package-private method used by AudioStream.join(). - void add(AudioStream stream, AudioCodec codec, int dtmfType) { - synchronized (this) { - if (!mStreams.containsKey(stream)) { - try { - int socket = stream.dup(); - String codecSpec = String.format("%d %s %s", codec.type, - codec.rtpmap, codec.fmtp); - nativeAdd(stream.getMode(), socket, - stream.getRemoteAddress().getHostAddress(), - stream.getRemotePort(), codecSpec, dtmfType); - mStreams.put(stream, socket); - } catch (NullPointerException e) { - throw new IllegalStateException(e); - } + synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { + if (!mStreams.containsKey(stream)) { + try { + int socket = stream.dup(); + String codecSpec = String.format("%d %s %s", codec.type, + codec.rtpmap, codec.fmtp); + nativeAdd(stream.getMode(), socket, + stream.getRemoteAddress().getHostAddress(), + stream.getRemotePort(), codecSpec, dtmfType); + mStreams.put(stream, socket); + } catch (NullPointerException e) { + throw new IllegalStateException(e); } } } @@ -159,12 +163,10 @@ public class AudioGroup { int remotePort, String codecSpec, int dtmfType); // Package-private method used by AudioStream.join(). - void remove(AudioStream stream) { - synchronized (this) { - Integer socket = mStreams.remove(stream); - if (socket != null) { - nativeRemove(socket); - } + synchronized void remove(AudioStream stream) { + Integer socket = mStreams.remove(stream); + if (socket != null) { + nativeRemove(socket); } } diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index 0edae6b..b45cc5e 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -27,8 +27,8 @@ import java.net.SocketException; * configured {@link AudioCodec}. On the other side, An {@link AudioGroup} * represents a local endpoint which mixes all the AudioStreams and optionally * interacts with the speaker and the microphone at the same time. The simplest - * usage includes one for each endpoints. For other combinations, users should - * be aware of the limitations described in {@link AudioGroup}. + * usage includes one for each endpoints. For other combinations, developers + * should be aware of the limitations described in {@link AudioGroup}. * *

    An AudioStream becomes busy when it joins an AudioGroup. In this case most * of the setter methods are disabled. This is designed to ease the task of -- cgit v1.2.3 From d0b41192e3073e9be0cf84206bf1ce6ac629a790 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 3 Mar 2011 08:58:05 +0800 Subject: NEW_API: Unhide RTP APIs. This change unhides RTP related classes including AudioCodec, AudioGroup, AudioStream, and RtpStream. This allows developers to control audio streams directly and also makes conference calls possible with the combination of the public SIP APIs. Change-Id: Idfd4edf65a1cbf3245ec2786fbc03b06438b0fb3 --- java/android/net/rtp/AudioCodec.java | 1 - java/android/net/rtp/AudioGroup.java | 1 - java/android/net/rtp/AudioStream.java | 1 - java/android/net/rtp/RtpStream.java | 1 - 4 files changed, 4 deletions(-) diff --git a/java/android/net/rtp/AudioCodec.java b/java/android/net/rtp/AudioCodec.java index 3877aeb..85255c8 100644 --- a/java/android/net/rtp/AudioCodec.java +++ b/java/android/net/rtp/AudioCodec.java @@ -33,7 +33,6 @@ import java.util.Arrays; * * * @see AudioStream - * @hide */ public class AudioCodec { /** diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index 20c8969..3e7ace8 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -59,7 +59,6 @@ import java.util.Map; * the AudioGroups is in use.

    * * @see AudioStream - * @hide */ public class AudioGroup { /** diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index b45cc5e..d761214 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -41,7 +41,6 @@ import java.net.SocketException; * * @see RtpStream * @see AudioGroup - * @hide */ public class AudioStream extends RtpStream { private AudioCodec mCodec; diff --git a/java/android/net/rtp/RtpStream.java b/java/android/net/rtp/RtpStream.java index 87d8bc6..e94ac42 100644 --- a/java/android/net/rtp/RtpStream.java +++ b/java/android/net/rtp/RtpStream.java @@ -27,7 +27,6 @@ import java.net.SocketException; * *

    Using this class requires * {@link android.Manifest.permission#INTERNET} permission.

    - * @hide */ public class RtpStream { /** -- cgit v1.2.3 From 18daeab2b2f4a4a0258f7ba03f8f1a83e1baac3f Mon Sep 17 00:00:00 2001 From: Iliyan Malchev Date: Mon, 14 Mar 2011 14:02:13 -0700 Subject: frameworks/base: remove LOCAL_PRELINK_MODULE Change-Id: I54dd62ebef47e7690afa5a858f3cad941b135481 Signed-off-by: Iliyan Malchev --- jni/rtp/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 76c43ba..61680a2 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -53,6 +53,6 @@ LOCAL_C_INCLUDES += \ LOCAL_CFLAGS += -fvisibility=hidden -LOCAL_PRELINK_MODULE := false + include $(BUILD_SHARED_LIBRARY) -- cgit v1.2.3 From 984a78d3cd5c6e31b9fbba7614fbe1cc431adb1c Mon Sep 17 00:00:00 2001 From: Carl Shapiro Date: Mon, 21 Mar 2011 20:21:40 -0700 Subject: Include strings.h instead of string.h for the strcasecmp prototype. Change-Id: I6b0ddc2408c30851edcffb36f1bc74245403ffc7 --- jni/rtp/AudioCodec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/AudioCodec.cpp b/jni/rtp/AudioCodec.cpp index 2267ea0..c75fbc9 100644 --- a/jni/rtp/AudioCodec.cpp +++ b/jni/rtp/AudioCodec.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include #include "AudioCodec.h" -- cgit v1.2.3 From cf95f3b0500ae6f8e4965a05fa72b17fc49eafa3 Mon Sep 17 00:00:00 2001 From: Magnus Strandberg Date: Tue, 22 Mar 2011 08:03:58 +0100 Subject: Making it possible to call SIP calls with special allowed chars. Since String.replaceFirst uses regex and since SIP user names are allowed to include regex charaters such as '+', the code must fist convert the string to a literal pattern String before using replaceFirst method. Change-Id: I25eac852bd620724ca1c5b2befc023af9dae3c1a --- java/com/android/server/sip/SipHelper.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 518543a..f24e3fb 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -27,6 +27,8 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.EventObject; import java.util.List; +import java.util.regex.Pattern; + import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; @@ -215,9 +217,11 @@ class SipHelper { String tag) throws ParseException, SipException { FromHeader fromHeader = createFromHeader(userProfile, tag); ToHeader toHeader = createToHeader(userProfile); + + String replaceStr = Pattern.quote(userProfile.getUserName() + "@"); SipURI requestURI = mAddressFactory.createSipURI( - userProfile.getUriString().replaceFirst( - userProfile.getUserName() + "@", "")); + userProfile.getUriString().replaceFirst(replaceStr, "")); + List viaHeaders = createViaHeaders(); CallIdHeader callIdHeader = createCallIdHeader(); CSeqHeader cSeqHeader = createCSeqHeader(requestType); -- cgit v1.2.3 From 81b8e1b53a6035d0950309dc24d1500c5acf7f37 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Tue, 29 Mar 2011 18:22:57 -0700 Subject: Issue 4157048: mic gain for VoIP/SIP calls. Herring board exhibits a strong echo even in non speakerphone modes. To compensate the lack of AEC or AES when not in speakerphone, the mic gain had been reduced in the ADC. But this has an adverse effect on other VoIP applications that have their own AEC and are penalized by the weak mic gain. This workaround enables an acceptable mic gain for other VoIP apps while offering a SIP call experience which is not worse than it was with the residual echo that was present even with mic gain reduction. Change-Id: I33fd37858758e94e42ef5b545d3f0dc233220bf1 --- jni/rtp/AudioGroup.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 2cbd023..ae071ce 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -30,6 +30,7 @@ #define LOG_TAG "AudioGroup" #include +#include #include #include #include @@ -619,6 +620,14 @@ bool AudioGroup::setMode(int mode) if (mode < 0 || mode > LAST_MODE) { return false; } + //FIXME: temporary code to overcome echo and mic gain issues on herring board. + // Must be modified/removed when proper support for voice processing query and control + // is included in audio framework + char value[PROPERTY_VALUE_MAX]; + property_get("ro.product.board", value, ""); + if (mode == NORMAL && !strcmp(value, "herring")) { + mode = ECHO_SUPPRESSION; + } if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters( 0, String8("ec_supported")) == "ec_supported=yes") { mode = NORMAL; -- cgit v1.2.3 From 31a824ddf74276c6f571079fe6afbb71c13bf965 Mon Sep 17 00:00:00 2001 From: Dima Zavin Date: Tue, 19 Apr 2011 22:30:36 -0700 Subject: audio/media: convert to using the audio HAL and new audio defs Change-Id: Ibc637918637329e4f2b62f4ac7781102fbc269f5 Signed-off-by: Dima Zavin --- jni/rtp/AudioGroup.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 41fedce..8b31996 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -41,6 +41,8 @@ #include #include +#include + #include "jni.h" #include "JNIHelp.h" @@ -767,10 +769,10 @@ bool AudioGroup::DeviceThread::threadLoop() // Find out the frame count for AudioTrack and AudioRecord. int output = 0; int input = 0; - if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, + if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL, sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, - AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) { LOGE("cannot compute frame count"); return false; } @@ -787,10 +789,10 @@ bool AudioGroup::DeviceThread::threadLoop() // Initialize AudioTrack and AudioRecord. AudioTrack track; AudioRecord record; - if (track.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( - AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AudioSystem::PCM_16_BIT, - AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { + if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT, + AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( + AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT, + AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) { LOGE("cannot initialize audio device"); return false; } -- cgit v1.2.3 From abed3b90dfe961d276cbedda60f4d1a92d81d2b9 Mon Sep 17 00:00:00 2001 From: Scott Main Date: Thu, 5 May 2011 16:17:40 -0700 Subject: docs: add package description for RTP Change-Id: I02c181a48101be288fb4aabf497f573f00038f90 --- java/android/net/rtp/package.html | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 java/android/net/rtp/package.html diff --git a/java/android/net/rtp/package.html b/java/android/net/rtp/package.html new file mode 100644 index 0000000..4506b09 --- /dev/null +++ b/java/android/net/rtp/package.html @@ -0,0 +1,28 @@ + + +

    Provides APIs for RTP (Real-time Transport Protocol), allowing applications to manage on-demand +or interactive data streaming. In particular, apps that provide VOIP, push-to-talk, conferencing, +and audio streaming can use these APIs to initiate sessions and transmit or receive data streams +over any available network.

    + +

    To support audio conferencing and similar usages, you need to instantiate two classes as +endpoints for the stream:

    + +
      +
    • {@link android.net.rtp.AudioStream} specifies a remote endpoint and consists of network mapping +and a configured {@link android.net.rtp.AudioCodec}.
    • + +
    • {@link android.net.rtp.AudioGroup} represents the local endpoint for one or more {@link +android.net.rtp.AudioStream}s. The {@link android.net.rtp.AudioGroup} mixes all the {@link +android.net.rtp.AudioStream}s and optionally interacts with the device speaker and the microphone at +the same time.
    • +
    + +

    The simplest usage involves a single remote endpoint and local endpoint. For more complex usages, +refer to the limitations described for {@link android.net.rtp.AudioGroup}.

    + +

    Note: To use the RTP APIs, you must request the {@link +android.Manifest.permission#INTERNET} and {@link +android.Manifest.permission#RECORD_AUDIO} permissions in your manifest file.

    + + \ No newline at end of file -- cgit v1.2.3 From b4d2c9aebb43b37d904b5c50c8be5e59048ef594 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 11 May 2011 14:13:42 -0700 Subject: Squashed commit of the following: commit c80992e419ed567abef451042f09c4958534b90d Author: Andreas Huber Date: Wed May 11 14:00:07 2011 -0700 Support for the mp3 audio decoder as a software OMX component. Change-Id: I66e10c4d0be4c3aecdef1c21b15a2c7359ceb807 commit a358d0e1bf2a88897887445f42ccdda0f5f2f528 Author: Andreas Huber Date: Wed May 11 13:11:23 2011 -0700 Support for G.711 alaw and mulaw decoders as software OMX components Change-Id: Ia5c76c02cb83a9f94ce39a27b2251e5880218f03 commit 79088b9c9a5c8b8c97ea66cb4f90a2b0f0d34553 Author: Andreas Huber Date: Thu May 5 15:43:32 2011 -0700 Instead of using an RGB surface and conversion yuv420->rgb565 convert from OMX_COLOR_FormatYUV420Planar to HAL_PIXEL_FORMAT_YV12 instead. Change-Id: I8c4fc3c54c963f0d4ba6377f3c4ab4e0013152e5 related-to-bug: 4394005 commit 69469d3bd84425777b11b9fc938c5e0c61af26a7 Author: Andreas Huber Date: Tue May 10 15:46:42 2011 -0700 voip mustn't link against libstagefright.so Change-Id: I4d0ba9a8b9dc9380b792a1bd04bcda231964862c commit 2a9a9eeeeeb36ae3a9e680469c3016d509ff08c3 Author: Andreas Huber Date: Tue May 10 14:37:10 2011 -0700 Remove most non-OMX software decoders by default Change-Id: Ic56514bc1b56b8fa952e8c4a164ea7379ecb69d0 commit a4de62c37b335c318217765403a9fb282b20a216 Author: Andreas Huber Date: Mon May 9 16:50:02 2011 -0700 Conditionally build the old-style software decoders. Change-Id: I5de609e1d76c92d26d6eb81d1551462258f3f15f commit 5d8b039f9449dc3dad1e77c42c80cc0b54b0c846 Author: Andreas Huber Date: Mon May 9 16:13:12 2011 -0700 Support for MPEG4 and H.263 video decoders as soft OMX components. Change-Id: I5e3a4835afab89f98e3aa128d013628f5830eafe commit b25a1bfbeb0ff6e62e1cc694ce2599c91489c7d0 Author: Andreas Huber Date: Mon May 9 11:49:10 2011 -0700 Boost Soft OMX thread priority, fix timestamp handling in vorbis Soft OMX decoder. Change-Id: I68d26d4999f06fcc451d69e5303663fab0cba9e8 commit c0574362f8dc3319ce84d981097867062a698527 Author: Andreas Huber Date: Mon May 9 11:28:53 2011 -0700 Support for the AMR decoders (NB and WB) as Soft OMX components. Change-Id: Ia565f59833fb52653e23f26536e7e41fc329a754 commit 3e5575a8f0e27a490cb7bde77bd9456087837f08 Author: Andreas Huber Date: Wed May 4 13:41:25 2011 -0700 Signal an error if the aac decoder failed to initialize from codec specific data. Change-Id: I01da7831bdf722edd7d6dc5974486daa2cf2b209 related-to-bug: 4272179 commit f94aeaa9886e772ff4823e671ed237096649f4af Author: Andreas Huber Date: Tue May 3 13:07:38 2011 -0700 Software OMX nodes don't (yet?) support native_window mode. Change-Id: I7d9ca9164ef4abf66b573ca21dba12d672f8b12d commit eefdfabac8dc659e00daa56da69aea705c49cb67 Author: Andreas Huber Date: Tue May 3 12:57:16 2011 -0700 Fixing the OMX tests to refer to appropriate files from test content. Change-Id: I5b61c3498749bfb876abbd3946a5132356e3f6ff commit f31b7326aef14b6a1b7946520a9688f092e844d5 Author: Andreas Huber Date: Tue May 3 11:08:38 2011 -0700 Soft OMX components are now dynamiclly loaded/unloaded, not directly linked against. Change-Id: I1e2ecfbfab67a8869886f738eaf0c7b3c948b6d9 commit b7f0343879e4df06f0a1c9bfece24df557954e2f Author: Andreas Huber Date: Mon May 2 15:58:36 2011 -0700 Support for the AVC software decoder as an OMX component. Change-Id: I13c12df435ba4afbd968a9fc659f66b91c818bc2 commit 5bb9e616d6c8e1b13d531fe996b9a9affdfb2977 Author: Andreas Huber Date: Fri Apr 29 12:05:37 2011 -0700 Fix Vorbis OMX decoder's component role. Change-Id: I5e871e5e11b3f951c93590210e63fd7987c467b5 commit 089c91f2333062e196c7afd5fb0ca914878aa474 Author: Andreas Huber Date: Fri Apr 29 12:05:18 2011 -0700 Support vorbis_decoder OMX testing. Change-Id: I1985be178a12ae3f8768bc72067d9236238be170 commit 56e241fa36fc37219bc536b823bdc2ab82dc1fad Author: Andreas Huber Date: Fri Apr 29 12:01:46 2011 -0700 SoftVorbis OMX component now respects the number of valid frames per page. Change-Id: I82a117a064d9b083fc58a54ad900a987a763ef03 commit fcd618ec520c376fdb78f4cbb44b8d9f5d213e2b Author: Andreas Huber Date: Fri Apr 29 10:59:38 2011 -0700 Support for the vorbis audio decoder as a soft OMX component. Change-Id: Iaeb057e58ca306d3dce205c0445b74d5aefef492 commit d1fcc3203fc8003ad79c6e96b3a1fc4261743f16 Author: Andreas Huber Date: Fri Apr 29 10:07:50 2011 -0700 VPX decoder now properly resizes buffers after a port settings change. Change-Id: I110749a31b6cba087891d8e5dfe420830bdbf831 commit 35c7168243cb69849d88911144a2c7fdfed5c54e Author: Andreas Huber Date: Thu Apr 28 13:23:34 2011 -0700 Support for the VPX video decoder as a Software OMX component. Change-Id: Ic345add2d6d768d4af631160153f2e9b97fcea71 commit 923b2534b4211fc5405377b5190bfa6f2dd27f32 Author: Andreas Huber Date: Thu Apr 28 11:34:40 2011 -0700 Table-based registration of soft omx components. Change-Id: I7f45f0fa5b3a7950776e69c66349731f7674e937 commit 04a88f3edb2266a463da9c4481b80178be460902 Author: Andreas Huber Date: Thu Apr 28 11:22:31 2011 -0700 Apparently OMX_GetParameter is valid in any state other than OMX_StateInvalid OMX_SetParameter is still constrained to OMX_StateLoaded or a disabled port. Change-Id: I1032d7cf4011982d306aa369d4158a82830d26fb commit 9d70ca68445e7c40f5c9b2d12466e468f514de88 Author: Andreas Huber Date: Wed Apr 27 15:03:18 2011 -0700 Use the new soft OMX aac decoder for HTTP live playback. Change-Id: Ifbcfb732a9edb855cb46b49f6d0ac942170ee28f commit 213fe4a10ea93cce08e8622dc3908053f29878a1 Author: Andreas Huber Date: Tue Apr 12 16:39:45 2011 -0700 Foundation for supporting software decoders as OMX components Change-Id: I7fdab256563b35d1d090617abaea9a26b198d816 Change-Id: I83e9236beed4af985d10333c203f065df9e09a42 --- jni/rtp/Android.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 61680a2..47ed658 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -37,9 +37,9 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ - libstagefright + libstagefright_amrnb_common -LOCAL_STATIC_LIBRARIES := libgsm +LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ -- cgit v1.2.3 From 36daedc9bcdadecc3065b2783553804e81a78dcd Mon Sep 17 00:00:00 2001 From: Dima Zavin Date: Wed, 11 May 2011 14:15:23 -0700 Subject: update for new audio.h header location Change-Id: Ic4c62c4037800802427eb7d3c7f5eb8b25d18876 Signed-off-by: Dima Zavin --- jni/rtp/AudioGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 8b31996..85ed1c8 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -41,7 +41,7 @@ #include #include -#include +#include #include "jni.h" #include "JNIHelp.h" -- cgit v1.2.3 From 469491e7807a46f31681d21c0ddae215f7891094 Mon Sep 17 00:00:00 2001 From: Chung-yih Wang Date: Thu, 10 Mar 2011 11:33:39 +0800 Subject: Add KeepAlive Interval Measurement. Change-Id: Id5ea2fcfa0bcd45198e773a5842d39eacc8ae400 --- java/com/android/server/sip/SipService.java | 125 +++++++++++++++++++++++ java/com/android/server/sip/SipSessionGroup.java | 3 +- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index dc66989..afa696c 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -82,6 +82,7 @@ public final class SipService extends ISipService.Stub { private WifiScanProcess mWifiScanProcess; private WifiManager.WifiLock mWifiLock; private boolean mWifiOnly; + private IntervalMeasurementProcess mIntervalMeasurementProcess; private MyExecutor mExecutor; @@ -96,6 +97,7 @@ public final class SipService extends ISipService.Stub { private ConnectivityReceiver mConnectivityReceiver; private boolean mWifiEnabled; private SipWakeLock mMyWakeLock; + private int mKeepAliveInterval; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -441,12 +443,14 @@ public final class SipService extends ISipService.Stub { if (connected) { mLocalIp = determineLocalIp(); + mKeepAliveInterval = -1; for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } if (isWifi && (mWifiLock != null)) stopWifiScanner(); } else { mMyWakeLock.reset(); // in case there's a leak + stopPortMappingMeasurement(); if (isWifi && (mWifiLock != null)) startWifiScanner(); } } catch (SipException e) { @@ -454,6 +458,18 @@ public final class SipService extends ISipService.Stub { } } + private void stopPortMappingMeasurement() { + if (mIntervalMeasurementProcess != null) { + mIntervalMeasurementProcess.stop(); + mIntervalMeasurementProcess = null; + } + } + + private void startPortMappingLifetimeMeasurement(SipSessionGroup group) { + mIntervalMeasurementProcess = new IntervalMeasurementProcess(group); + mIntervalMeasurementProcess.start(); + } + private synchronized void addPendingSession(ISipSession session) { try { cleanUpPendingSessions(); @@ -687,6 +703,93 @@ public final class SipService extends ISipService.Stub { } } + private class IntervalMeasurementProcess extends SipSessionAdapter + implements Runnable { + private static final String TAG = "\\INTERVAL/"; + private static final int MAX_INTERVAL = 120; // seconds + private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME; + private static final int PASS_THRESHOLD = 6; + private SipSessionGroupExt mGroup; + private SipSessionGroup.SipSessionImpl mSession; + private boolean mRunning; + private int mMinInterval = 10; + private int mMaxInterval = MAX_INTERVAL; + private int mInterval = MAX_INTERVAL / 2; + private int mPassCounter = 0; + private WakeupTimer mTimer = new WakeupTimer(mContext); + + + public IntervalMeasurementProcess(SipSessionGroup group) { + try { + mGroup = new SipSessionGroupExt( + group.getLocalProfile(), null, null); + mSession = (SipSessionGroup.SipSessionImpl) + mGroup.createSession(this); + } catch (Exception e) { + Log.w(TAG, "start interval measurement error: " + e); + } + } + + public void start() { + if (mRunning) return; + mRunning = true; + mTimer.set(mInterval * 1000, this); + if (DEBUGV) Log.v(TAG, "start interval measurement"); + run(); + } + + public void stop() { + mRunning = false; + mTimer.cancel(this); + } + + private void restart() { + mTimer.cancel(this); + mTimer.set(mInterval * 1000, this); + } + + private void calculateNewInterval() { + if (!mSession.isReRegisterRequired()) { + if (++mPassCounter != PASS_THRESHOLD) return; + // update the interval, since the current interval is good to + // keep the port mapping. + mKeepAliveInterval = mMinInterval = mInterval; + } else { + // Since the rport is changed, shorten the interval. + mSession.clearReRegisterRequired(); + mMaxInterval = mInterval; + } + if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { + // update mKeepAliveInterval and stop measurement. + stop(); + mKeepAliveInterval = mMinInterval; + if (DEBUGV) Log.v(TAG, "measured interval: " + mKeepAliveInterval); + } else { + // calculate the new interval and continue. + mInterval = (mMaxInterval + mMinInterval) / 2; + mPassCounter = 0; + if (DEBUGV) { + Log.v(TAG, " current interval: " + mKeepAliveInterval + + "test new interval: " + mInterval); + } + restart(); + } + } + + public void run() { + synchronized (SipService.this) { + if (!mRunning) return; + try { + mSession.sendKeepAlive(); + calculateNewInterval(); + } catch (Throwable t) { + stop(); + Log.w(TAG, "interval measurement error: " + t); + } + } + } + } + // KeepAliveProcess is controlled by AutoRegistrationProcess. // All methods will be invoked in sync with SipService.this. private class KeepAliveProcess implements Runnable { @@ -694,6 +797,7 @@ public final class SipService extends ISipService.Stub { private static final int INTERVAL = 10; private SipSessionGroup.SipSessionImpl mSession; private boolean mRunning = false; + private int mInterval = INTERVAL; public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { mSession = session; @@ -705,6 +809,12 @@ public final class SipService extends ISipService.Stub { mTimer.set(INTERVAL * 1000, this); } + private void restart(int duration) { + if (DEBUG) Log.d(TAG, "Refresh NAT port mapping " + duration + "s later."); + mTimer.cancel(this); + mTimer.set(duration * 1000, this); + } + // timeout handler public void run() { synchronized (SipService.this) { @@ -721,6 +831,10 @@ public final class SipService extends ISipService.Stub { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); } + if (mKeepAliveInterval > mInterval) { + mInterval = mKeepAliveInterval; + restart(mInterval); + } } catch (Throwable t) { Log.w(TAG, "keepalive error: " + t); } @@ -761,6 +875,17 @@ public final class SipService extends ISipService.Stub { // return right away if no active network connection. if (mSession == null) return; + synchronized (SipService.this) { + if (isBehindNAT(mLocalIp) + && (mIntervalMeasurementProcess == null) + && (mKeepAliveInterval == -1)) { + // Start keep-alive interval measurement, here we allow + // the first profile only as the target service provider + // to measure the life time of NAT port mapping. + startPortMappingLifetimeMeasurement(group); + } + } + // start unregistration to clear up old registration at server // TODO: when rfc5626 is deployed, use reg-id and sip.instance // in registration to avoid adding duplicate entries to server diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index aa616a9..b5f1a17 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -397,7 +397,7 @@ class SipSessionGroup implements SipListener { // for registration boolean mReRegisterFlag = false; - int mRPort; + int mRPort = 0; // lightweight timer class SessionTimer { @@ -447,7 +447,6 @@ class SipSessionGroup implements SipListener { mState = SipSession.State.READY_TO_CALL; mInviteReceived = null; mPeerSessionDescription = null; - mRPort = 0; mAuthenticationRetryCount = 0; if (mDialog != null) mDialog.delete(); -- cgit v1.2.3 From aef08f1dd08902d0260dec34b2feede626d1bdc6 Mon Sep 17 00:00:00 2001 From: repo sync Date: Mon, 13 Jun 2011 17:07:41 +0800 Subject: Fix the issue of onNetwork in UI thread. bug:458435 This will temporarily start a thread for answering calls, we are going to add a handler thread to handle this soon. Change-Id: I9079038d671e1b1631c6e663fc2c3de297d97428 --- java/com/android/server/sip/SipSessionGroup.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index b5f1a17..4837eb9 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -528,12 +528,8 @@ class SipSessionGroup implements SipListener { public void answerCall(String sessionDescription, int timeout) { synchronized (SipSessionGroup.this) { if (mPeerProfile == null) return; - try { - processCommand(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } catch (SipException e) { - onError(e); - } + doCommandAsync(new MakeCallCommand(mPeerProfile, + sessionDescription, timeout)); } } -- cgit v1.2.3 From 4cbf2d2f94aaa094dda5b93357c93234ebccb120 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 14 Jun 2011 16:54:18 +0800 Subject: Move WakeupTimer out of SipService. This is to prepare to move keepalive process to SipSessionGroup before fixing the following bug. Bug: 3464181 Change-Id: I57d8f6effad76706b5a76e1269c53d558db88ae4 --- java/com/android/server/sip/SipService.java | 323 +--------------------- java/com/android/server/sip/SipWakeupTimer.java | 339 ++++++++++++++++++++++++ 2 files changed, 351 insertions(+), 311 deletions(-) create mode 100644 java/com/android/server/sip/SipWakeupTimer.java diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index afa696c..5ad5d26 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -60,6 +60,7 @@ import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.TreeSet; +import java.util.concurrent.Executor; import javax.sip.SipException; /** @@ -68,8 +69,7 @@ import javax.sip.SipException; public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; static final boolean DEBUGV = false; - private static final boolean DEBUG = false; - private static final boolean DEBUG_TIMER = DEBUG && false; + static final boolean DEBUG = false; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; @@ -78,13 +78,13 @@ public final class SipService extends ISipService.Stub { private String mLocalIp; private String mNetworkType; private boolean mConnected; - private WakeupTimer mTimer; + private SipWakeupTimer mTimer; private WifiScanProcess mWifiScanProcess; private WifiManager.WifiLock mWifiLock; private boolean mWifiOnly; private IntervalMeasurementProcess mIntervalMeasurementProcess; - private MyExecutor mExecutor; + private MyExecutor mExecutor = new MyExecutor(); // SipProfile URI --> group private Map mSipGroups = @@ -118,7 +118,7 @@ public final class SipService extends ISipService.Stub { mMyWakeLock = new SipWakeLock((PowerManager) context.getSystemService(Context.POWER_SERVICE)); - mTimer = new WakeupTimer(context); + mTimer = new SipWakeupTimer(context, mExecutor); mWifiOnly = SipManager.isSipWifiOnly(context); } @@ -159,12 +159,6 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " --- unregister receivers"); } - private MyExecutor getExecutor() { - // create mExecutor lazily - if (mExecutor == null) mExecutor = new MyExecutor(); - return mExecutor; - } - public synchronized SipProfile[] getListOfProfiles() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -716,8 +710,8 @@ public final class SipService extends ISipService.Stub { private int mMaxInterval = MAX_INTERVAL; private int mInterval = MAX_INTERVAL / 2; private int mPassCounter = 0; - private WakeupTimer mTimer = new WakeupTimer(mContext); - + private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor); + // TODO: fix SipWakeupTimer so that we only use one instance of the timer public IntervalMeasurementProcess(SipSessionGroup group) { try { @@ -1123,7 +1117,7 @@ public final class SipService extends ISipService.Stub { @Override public void onReceive(final Context context, final Intent intent) { // Run the handler in MyExecutor to be protected by wake lock - getExecutor().execute(new Runnable() { + mExecutor.execute(new Runnable() { public void run() { onReceiveInternal(context, intent); } @@ -1227,7 +1221,7 @@ public final class SipService extends ISipService.Stub { @Override public void run() { // delegate to mExecutor - getExecutor().execute(new Runnable() { + mExecutor.execute(new Runnable() { public void run() { realRun(); } @@ -1252,300 +1246,6 @@ public final class SipService extends ISipService.Stub { } } - /** - * Timer that can schedule events to occur even when the device is in sleep. - * Only used internally in this package. - */ - class WakeupTimer extends BroadcastReceiver { - private static final String TAG = "_SIP.WkTimer_"; - private static final String TRIGGER_TIME = "TriggerTime"; - - private Context mContext; - private AlarmManager mAlarmManager; - - // runnable --> time to execute in SystemClock - private TreeSet mEventQueue = - new TreeSet(new MyEventComparator()); - - private PendingIntent mPendingIntent; - - public WakeupTimer(Context context) { - mContext = context; - mAlarmManager = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - - IntentFilter filter = new IntentFilter(getAction()); - context.registerReceiver(this, filter); - } - - /** - * Stops the timer. No event can be scheduled after this method is called. - */ - public synchronized void stop() { - mContext.unregisterReceiver(this); - if (mPendingIntent != null) { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - mEventQueue.clear(); - mEventQueue = null; - } - - private synchronized boolean stopped() { - if (mEventQueue == null) { - Log.w(TAG, "Timer stopped"); - return true; - } else { - return false; - } - } - - private void cancelAlarm() { - mAlarmManager.cancel(mPendingIntent); - mPendingIntent = null; - } - - private void recalculatePeriods() { - if (mEventQueue.isEmpty()) return; - - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mMaxPeriod; - long minTriggerTime = firstEvent.mTriggerTime; - for (MyEvent e : mEventQueue) { - e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; - int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod - - minTriggerTime); - interval = interval / minPeriod * minPeriod; - e.mTriggerTime = minTriggerTime + interval; - } - TreeSet newQueue = new TreeSet( - mEventQueue.comparator()); - newQueue.addAll((Collection) mEventQueue); - mEventQueue.clear(); - mEventQueue = newQueue; - if (DEBUG_TIMER) { - Log.d(TAG, "queue re-calculated"); - printQueue(); - } - } - - // Determines the period and the trigger time of the new event and insert it - // to the queue. - private void insertEvent(MyEvent event) { - long now = SystemClock.elapsedRealtime(); - if (mEventQueue.isEmpty()) { - event.mTriggerTime = now + event.mPeriod; - mEventQueue.add(event); - return; - } - MyEvent firstEvent = mEventQueue.first(); - int minPeriod = firstEvent.mPeriod; - if (minPeriod <= event.mMaxPeriod) { - event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; - int interval = event.mMaxPeriod; - interval -= (int) (firstEvent.mTriggerTime - now); - interval = interval / minPeriod * minPeriod; - event.mTriggerTime = firstEvent.mTriggerTime + interval; - mEventQueue.add(event); - } else { - long triggerTime = now + event.mPeriod; - if (firstEvent.mTriggerTime < triggerTime) { - event.mTriggerTime = firstEvent.mTriggerTime; - event.mLastTriggerTime -= event.mPeriod; - } else { - event.mTriggerTime = triggerTime; - } - mEventQueue.add(event); - recalculatePeriods(); - } - } - - /** - * Sets a periodic timer. - * - * @param period the timer period; in milli-second - * @param callback is called back when the timer goes off; the same callback - * can be specified in multiple timer events - */ - public synchronized void set(int period, Runnable callback) { - if (stopped()) return; - - long now = SystemClock.elapsedRealtime(); - MyEvent event = new MyEvent(period, callback, now); - insertEvent(event); - - if (mEventQueue.first() == event) { - if (mEventQueue.size() > 1) cancelAlarm(); - scheduleNext(); - } - - long triggerTime = event.mTriggerTime; - if (DEBUG_TIMER) { - Log.d(TAG, " add event " + event + " scheduled at " - + showTime(triggerTime) + " at " + showTime(now) - + ", #events=" + mEventQueue.size()); - printQueue(); - } - } - - /** - * Cancels all the timer events with the specified callback. - * - * @param callback the callback - */ - public synchronized void cancel(Runnable callback) { - if (stopped() || mEventQueue.isEmpty()) return; - if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); - - MyEvent firstEvent = mEventQueue.first(); - for (Iterator iter = mEventQueue.iterator(); - iter.hasNext();) { - MyEvent event = iter.next(); - if (event.mCallback == callback) { - iter.remove(); - if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); - } - } - if (mEventQueue.isEmpty()) { - cancelAlarm(); - } else if (mEventQueue.first() != firstEvent) { - cancelAlarm(); - firstEvent = mEventQueue.first(); - firstEvent.mPeriod = firstEvent.mMaxPeriod; - firstEvent.mTriggerTime = firstEvent.mLastTriggerTime - + firstEvent.mPeriod; - recalculatePeriods(); - scheduleNext(); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after cancel:"); - printQueue(); - } - } - - private void scheduleNext() { - if (stopped() || mEventQueue.isEmpty()) return; - - if (mPendingIntent != null) { - throw new RuntimeException("pendingIntent is not null!"); - } - - MyEvent event = mEventQueue.first(); - Intent intent = new Intent(getAction()); - intent.putExtra(TRIGGER_TIME, event.mTriggerTime); - PendingIntent pendingIntent = mPendingIntent = - PendingIntent.getBroadcast(mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - event.mTriggerTime, pendingIntent); - } - - @Override - public void onReceive(Context context, Intent intent) { - // This callback is already protected by AlarmManager's wake lock. - String action = intent.getAction(); - if (getAction().equals(action) - && intent.getExtras().containsKey(TRIGGER_TIME)) { - mPendingIntent = null; - long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); - execute(triggerTime); - } else { - Log.d(TAG, "unrecognized intent: " + intent); - } - } - - private void printQueue() { - int count = 0; - for (MyEvent event : mEventQueue) { - Log.d(TAG, " " + event + ": scheduled at " - + showTime(event.mTriggerTime) + ": last at " - + showTime(event.mLastTriggerTime)); - if (++count >= 5) break; - } - if (mEventQueue.size() > count) { - Log.d(TAG, " ....."); - } else if (count == 0) { - Log.d(TAG, " "); - } - } - - private synchronized void execute(long triggerTime) { - if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " - + showTime(triggerTime) + ": " + mEventQueue.size()); - if (stopped() || mEventQueue.isEmpty()) return; - - for (MyEvent event : mEventQueue) { - if (event.mTriggerTime != triggerTime) break; - if (DEBUG_TIMER) Log.d(TAG, "execute " + event); - - event.mLastTriggerTime = event.mTriggerTime; - event.mTriggerTime += event.mPeriod; - - // run the callback in the handler thread to prevent deadlock - getExecutor().execute(event.mCallback); - } - if (DEBUG_TIMER) { - Log.d(TAG, "after timeout execution"); - printQueue(); - } - scheduleNext(); - } - - private String getAction() { - return toString(); - } - - private String showTime(long time) { - int ms = (int) (time % 1000); - int s = (int) (time / 1000); - int m = s / 60; - s %= 60; - return String.format("%d.%d.%d", m, s, ms); - } - } - - private static class MyEvent { - int mPeriod; - int mMaxPeriod; - long mTriggerTime; - long mLastTriggerTime; - Runnable mCallback; - - MyEvent(int period, Runnable callback, long now) { - mPeriod = mMaxPeriod = period; - mCallback = callback; - mLastTriggerTime = now; - } - - @Override - public String toString() { - String s = super.toString(); - s = s.substring(s.indexOf("@")); - return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" - + toString(mCallback); - } - - private String toString(Object o) { - String s = o.toString(); - int index = s.indexOf("$"); - if (index > 0) s = s.substring(index + 1); - return s; - } - } - - private static class MyEventComparator implements Comparator { - public int compare(MyEvent e1, MyEvent e2) { - if (e1 == e2) return 0; - int diff = e1.mMaxPeriod - e2.mMaxPeriod; - if (diff == 0) diff = -1; - return diff; - } - - public boolean equals(Object that) { - return (this == that); - } - } - private static Looper createLooper() { HandlerThread thread = new HandlerThread("SipService.Executor"); thread.start(); @@ -1554,12 +1254,13 @@ public final class SipService extends ISipService.Stub { // Executes immediate tasks in a single thread. // Hold/release wake lock for running tasks - private class MyExecutor extends Handler { + private class MyExecutor extends Handler implements Executor { MyExecutor() { super(createLooper()); } - void execute(Runnable task) { + @Override + public void execute(Runnable task) { mMyWakeLock.acquire(task); Message.obtain(this, 0/* don't care */, task).sendToTarget(); } diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java new file mode 100644 index 0000000..9cc26b0 --- /dev/null +++ b/java/com/android/server/sip/SipWakeupTimer.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2011, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.sip; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.SystemClock; +import android.util.Log; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeSet; +import java.util.concurrent.Executor; +import javax.sip.SipException; + +/** + * Timer that can schedule events to occur even when the device is in sleep. + */ +class SipWakeupTimer extends BroadcastReceiver { + private static final String TAG = "_SIP.WkTimer_"; + private static final String TRIGGER_TIME = "TriggerTime"; + private static final boolean DEBUG_TIMER = SipService.DEBUG && false; + + private Context mContext; + private AlarmManager mAlarmManager; + + // runnable --> time to execute in SystemClock + private TreeSet mEventQueue = + new TreeSet(new MyEventComparator()); + + private PendingIntent mPendingIntent; + + private Executor mExecutor; + + public SipWakeupTimer(Context context, Executor executor) { + mContext = context; + mAlarmManager = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + IntentFilter filter = new IntentFilter(getAction()); + context.registerReceiver(this, filter); + mExecutor = executor; + } + + /** + * Stops the timer. No event can be scheduled after this method is called. + */ + public synchronized void stop() { + mContext.unregisterReceiver(this); + if (mPendingIntent != null) { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + mEventQueue.clear(); + mEventQueue = null; + } + + private synchronized boolean stopped() { + if (mEventQueue == null) { + Log.w(TAG, "Timer stopped"); + return true; + } else { + return false; + } + } + + private void cancelAlarm() { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + + private void recalculatePeriods() { + if (mEventQueue.isEmpty()) return; + + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mMaxPeriod; + long minTriggerTime = firstEvent.mTriggerTime; + for (MyEvent e : mEventQueue) { + e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; + int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod + - minTriggerTime); + interval = interval / minPeriod * minPeriod; + e.mTriggerTime = minTriggerTime + interval; + } + TreeSet newQueue = new TreeSet( + mEventQueue.comparator()); + newQueue.addAll((Collection) mEventQueue); + mEventQueue.clear(); + mEventQueue = newQueue; + if (DEBUG_TIMER) { + Log.d(TAG, "queue re-calculated"); + printQueue(); + } + } + + // Determines the period and the trigger time of the new event and insert it + // to the queue. + private void insertEvent(MyEvent event) { + long now = SystemClock.elapsedRealtime(); + if (mEventQueue.isEmpty()) { + event.mTriggerTime = now + event.mPeriod; + mEventQueue.add(event); + return; + } + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mPeriod; + if (minPeriod <= event.mMaxPeriod) { + event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; + int interval = event.mMaxPeriod; + interval -= (int) (firstEvent.mTriggerTime - now); + interval = interval / minPeriod * minPeriod; + event.mTriggerTime = firstEvent.mTriggerTime + interval; + mEventQueue.add(event); + } else { + long triggerTime = now + event.mPeriod; + if (firstEvent.mTriggerTime < triggerTime) { + event.mTriggerTime = firstEvent.mTriggerTime; + event.mLastTriggerTime -= event.mPeriod; + } else { + event.mTriggerTime = triggerTime; + } + mEventQueue.add(event); + recalculatePeriods(); + } + } + + /** + * Sets a periodic timer. + * + * @param period the timer period; in milli-second + * @param callback is called back when the timer goes off; the same callback + * can be specified in multiple timer events + */ + public synchronized void set(int period, Runnable callback) { + if (stopped()) return; + + long now = SystemClock.elapsedRealtime(); + MyEvent event = new MyEvent(period, callback, now); + insertEvent(event); + + if (mEventQueue.first() == event) { + if (mEventQueue.size() > 1) cancelAlarm(); + scheduleNext(); + } + + long triggerTime = event.mTriggerTime; + if (DEBUG_TIMER) { + Log.d(TAG, " add event " + event + " scheduled at " + + showTime(triggerTime) + " at " + showTime(now) + + ", #events=" + mEventQueue.size()); + printQueue(); + } + } + + /** + * Cancels all the timer events with the specified callback. + * + * @param callback the callback + */ + public synchronized void cancel(Runnable callback) { + if (stopped() || mEventQueue.isEmpty()) return; + if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); + + MyEvent firstEvent = mEventQueue.first(); + for (Iterator iter = mEventQueue.iterator(); + iter.hasNext();) { + MyEvent event = iter.next(); + if (event.mCallback == callback) { + iter.remove(); + if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); + } + } + if (mEventQueue.isEmpty()) { + cancelAlarm(); + } else if (mEventQueue.first() != firstEvent) { + cancelAlarm(); + firstEvent = mEventQueue.first(); + firstEvent.mPeriod = firstEvent.mMaxPeriod; + firstEvent.mTriggerTime = firstEvent.mLastTriggerTime + + firstEvent.mPeriod; + recalculatePeriods(); + scheduleNext(); + } + if (DEBUG_TIMER) { + Log.d(TAG, "after cancel:"); + printQueue(); + } + } + + private void scheduleNext() { + if (stopped() || mEventQueue.isEmpty()) return; + + if (mPendingIntent != null) { + throw new RuntimeException("pendingIntent is not null!"); + } + + MyEvent event = mEventQueue.first(); + Intent intent = new Intent(getAction()); + intent.putExtra(TRIGGER_TIME, event.mTriggerTime); + PendingIntent pendingIntent = mPendingIntent = + PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + event.mTriggerTime, pendingIntent); + } + + @Override + public void onReceive(Context context, Intent intent) { + // This callback is already protected by AlarmManager's wake lock. + String action = intent.getAction(); + if (getAction().equals(action) + && intent.getExtras().containsKey(TRIGGER_TIME)) { + mPendingIntent = null; + long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); + execute(triggerTime); + } else { + Log.d(TAG, "unrecognized intent: " + intent); + } + } + + private void printQueue() { + int count = 0; + for (MyEvent event : mEventQueue) { + Log.d(TAG, " " + event + ": scheduled at " + + showTime(event.mTriggerTime) + ": last at " + + showTime(event.mLastTriggerTime)); + if (++count >= 5) break; + } + if (mEventQueue.size() > count) { + Log.d(TAG, " ....."); + } else if (count == 0) { + Log.d(TAG, " "); + } + } + + private synchronized void execute(long triggerTime) { + if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + + showTime(triggerTime) + ": " + mEventQueue.size()); + if (stopped() || mEventQueue.isEmpty()) return; + + for (MyEvent event : mEventQueue) { + if (event.mTriggerTime != triggerTime) break; + if (DEBUG_TIMER) Log.d(TAG, "execute " + event); + + event.mLastTriggerTime = event.mTriggerTime; + event.mTriggerTime += event.mPeriod; + + // run the callback in the handler thread to prevent deadlock + mExecutor.execute(event.mCallback); + } + if (DEBUG_TIMER) { + Log.d(TAG, "after timeout execution"); + printQueue(); + } + scheduleNext(); + } + + private String getAction() { + return toString(); + } + + private String showTime(long time) { + int ms = (int) (time % 1000); + int s = (int) (time / 1000); + int m = s / 60; + s %= 60; + return String.format("%d.%d.%d", m, s, ms); + } + + private static class MyEvent { + int mPeriod; + int mMaxPeriod; + long mTriggerTime; + long mLastTriggerTime; + Runnable mCallback; + + MyEvent(int period, Runnable callback, long now) { + mPeriod = mMaxPeriod = period; + mCallback = callback; + mLastTriggerTime = now; + } + + @Override + public String toString() { + String s = super.toString(); + s = s.substring(s.indexOf("@")); + return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" + + toString(mCallback); + } + + private String toString(Object o) { + String s = o.toString(); + int index = s.indexOf("$"); + if (index > 0) s = s.substring(index + 1); + return s; + } + } + + private static class MyEventComparator implements Comparator { + public int compare(MyEvent e1, MyEvent e2) { + if (e1 == e2) return 0; + int diff = e1.mMaxPeriod - e2.mMaxPeriod; + if (diff == 0) diff = -1; + return diff; + } + + public boolean equals(Object that) { + return (this == that); + } + } +} -- cgit v1.2.3 From 8a044dee1690c3dc7b5f467461ce4fa65c565a66 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 22 Jun 2011 16:42:38 +0800 Subject: Move the keepalive process to SipSessionImpl and make it reusable. Reuse the new component in the original keepalive process and the NAT port mapping timeout measurement process. This is the foundation for fixing the following bug. Bug: 3464181 Change-Id: If7e951c000503fa64843942ad062c4d853e20c8d --- java/com/android/server/sip/SipHelper.java | 51 ++-- java/com/android/server/sip/SipService.java | 334 +++++++++++----------- java/com/android/server/sip/SipSessionGroup.java | 338 ++++++++++++++++------- 3 files changed, 446 insertions(+), 277 deletions(-) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 4ee86b6..018e6de 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -71,6 +71,7 @@ import javax.sip.message.Response; class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); private static final boolean DEBUG = true; + private static final boolean DEBUG_PING = false; private SipStack mSipStack; private SipProvider mSipProvider; @@ -177,17 +178,19 @@ class SipHelper { return uri; } - public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) - throws SipException { + public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, + String tag) throws SipException { try { - Request request = createRequest(Request.OPTIONS, userProfile, tag); + Request request = (caller == callee) + ? createRequest(Request.OPTIONS, caller, tag) + : createRequest(Request.OPTIONS, caller, callee, tag); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); clientTransaction.sendRequest(); return clientTransaction; } catch (Exception e) { - throw new SipException("sendKeepAlive()", e); + throw new SipException("sendOptions()", e); } } @@ -249,23 +252,29 @@ class SipHelper { return ct; } + private Request createRequest(String requestType, SipProfile caller, + SipProfile callee, String tag) throws ParseException, SipException { + FromHeader fromHeader = createFromHeader(caller, tag); + ToHeader toHeader = createToHeader(callee); + SipURI requestURI = callee.getUri(); + List viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(requestType); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + + Request request = mMessageFactory.createRequest(requestURI, + requestType, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + + request.addHeader(createContactHeader(caller)); + return request; + } + public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag) throws SipException { try { - FromHeader fromHeader = createFromHeader(caller, tag); - ToHeader toHeader = createToHeader(callee); - SipURI requestURI = callee.getUri(); - List viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.INVITE, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(caller)); + Request request = createRequest(Request.INVITE, caller, callee, tag); request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); @@ -419,9 +428,13 @@ class SipHelper { public void sendResponse(RequestEvent event, int responseCode) throws SipException { try { + Request request = event.getRequest(); Response response = mMessageFactory.createResponse( - responseCode, event.getRequest()); - if (DEBUG) Log.d(TAG, "send response: " + response); + responseCode, request); + if (DEBUG && (!Request.OPTIONS.equals(request.getMethod()) + || DEBUG_PING)) { + Log.d(TAG, "send response: " + response); + } getServerTransaction(event).sendResponse(response); } catch (ParseException e) { throw new SipException("sendResponse()", e); diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 5ad5d26..802e56d 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -69,10 +69,11 @@ import javax.sip.SipException; public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; static final boolean DEBUGV = false; - static final boolean DEBUG = false; + static final boolean DEBUG = true; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; + private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds private Context mContext; private String mLocalIp; @@ -378,7 +379,7 @@ public final class SipService extends ISipService.Stub { private void grabWifiLock() { if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock"); + if (DEBUG) Log.d(TAG, "acquire wifi lock"); mWifiLock = ((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); @@ -389,7 +390,7 @@ public final class SipService extends ISipService.Stub { private void releaseWifiLock() { if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock"); + if (DEBUG) Log.d(TAG, "release wifi lock"); mWifiLock.release(); mWifiLock = null; stopWifiScanner(); @@ -459,9 +460,17 @@ public final class SipService extends ISipService.Stub { } } - private void startPortMappingLifetimeMeasurement(SipSessionGroup group) { - mIntervalMeasurementProcess = new IntervalMeasurementProcess(group); - mIntervalMeasurementProcess.start(); + private void startPortMappingLifetimeMeasurement( + SipProfile localProfile) { + if ((mIntervalMeasurementProcess == null) + && (mKeepAliveInterval == -1) + && isBehindNAT(mLocalIp)) { + Log.d(TAG, "start NAT port mapping timeout measurement on " + + localProfile.getUriString()); + + mIntervalMeasurementProcess = new IntervalMeasurementProcess(localProfile); + mIntervalMeasurementProcess.start(); + } } private synchronized void addPendingSession(ISipSession session) { @@ -500,6 +509,33 @@ public final class SipService extends ISipService.Stub { return false; } + private synchronized void onKeepAliveIntervalChanged() { + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onKeepAliveIntervalChanged(); + } + } + + private int getKeepAliveInterval() { + return (mKeepAliveInterval < 0) + ? DEFAULT_KEEPALIVE_INTERVAL + : mKeepAliveInterval; + } + + private boolean isBehindNAT(String address) { + try { + byte[] d = InetAddress.getByName(address).getAddress(); + if ((d[0] == 10) || + (((0x000000FF & ((int)d[0])) == 172) && + ((0x000000F0 & ((int)d[1])) == 16)) || + (((0x000000FF & ((int)d[0])) == 192) && + ((0x000000FF & ((int)d[1])) == 168))) { + return true; + } + } catch (UnknownHostException e) { + Log.e(TAG, "isBehindAT()" + address, e); + } + return false; + } private class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; @@ -527,6 +563,16 @@ public final class SipService extends ISipService.Stub { return mSipGroup.containsSession(callId); } + public void onKeepAliveIntervalChanged() { + mAutoRegistration.onKeepAliveIntervalChanged(); + } + + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mSipGroup.setWakeupTimer(timer); + } + // network connectivity is tricky because network can be disconnected // at any instant so need to deal with exceptions carefully even when // you think you are connected @@ -534,7 +580,7 @@ public final class SipService extends ISipService.Stub { SipProfile localProfile, String password) throws SipException { try { return new SipSessionGroup(localIp, localProfile, password, - mMyWakeLock); + mTimer, mMyWakeLock); } catch (IOException e) { // network disconnected Log.w(TAG, "createSipSessionGroup(): network disconnected?"); @@ -697,158 +743,114 @@ public final class SipService extends ISipService.Stub { } } - private class IntervalMeasurementProcess extends SipSessionAdapter - implements Runnable { - private static final String TAG = "\\INTERVAL/"; + private class IntervalMeasurementProcess implements + SipSessionGroup.KeepAliveProcessCallback { + private static final String TAG = "SipKeepAliveInterval"; private static final int MAX_INTERVAL = 120; // seconds private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME; - private static final int PASS_THRESHOLD = 6; + private static final int PASS_THRESHOLD = 10; private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; private boolean mRunning; - private int mMinInterval = 10; + private int mMinInterval = 10; // in seconds private int mMaxInterval = MAX_INTERVAL; private int mInterval = MAX_INTERVAL / 2; private int mPassCounter = 0; - private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor); - // TODO: fix SipWakeupTimer so that we only use one instance of the timer - public IntervalMeasurementProcess(SipSessionGroup group) { + public IntervalMeasurementProcess(SipProfile localProfile) { try { - mGroup = new SipSessionGroupExt( - group.getLocalProfile(), null, null); + mGroup = new SipSessionGroupExt(localProfile, null, null); + // TODO: remove this line once SipWakeupTimer can better handle + // variety of timeout values + mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); mSession = (SipSessionGroup.SipSessionImpl) - mGroup.createSession(this); + mGroup.createSession(null); } catch (Exception e) { Log.w(TAG, "start interval measurement error: " + e); } } public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(mInterval * 1000, this); - if (DEBUGV) Log.v(TAG, "start interval measurement"); - run(); + synchronized (SipService.this) { + try { + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "start()", e); + } + } } public void stop() { - mRunning = false; - mTimer.cancel(this); - } - - private void restart() { - mTimer.cancel(this); - mTimer.set(mInterval * 1000, this); - } - - private void calculateNewInterval() { - if (!mSession.isReRegisterRequired()) { - if (++mPassCounter != PASS_THRESHOLD) return; - // update the interval, since the current interval is good to - // keep the port mapping. - mKeepAliveInterval = mMinInterval = mInterval; - } else { - // Since the rport is changed, shorten the interval. - mSession.clearReRegisterRequired(); - mMaxInterval = mInterval; - } - if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { - // update mKeepAliveInterval and stop measurement. - stop(); - mKeepAliveInterval = mMinInterval; - if (DEBUGV) Log.v(TAG, "measured interval: " + mKeepAliveInterval); - } else { - // calculate the new interval and continue. - mInterval = (mMaxInterval + mMinInterval) / 2; - mPassCounter = 0; - if (DEBUGV) { - Log.v(TAG, " current interval: " + mKeepAliveInterval - + "test new interval: " + mInterval); - } - restart(); + synchronized (SipService.this) { + mSession.stopKeepAliveProcess(); } } - public void run() { + private void restart() { synchronized (SipService.this) { - if (!mRunning) return; try { - mSession.sendKeepAlive(); - calculateNewInterval(); - } catch (Throwable t) { - stop(); - Log.w(TAG, "interval measurement error: " + t); + mSession.stopKeepAliveProcess(); + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "restart()", e); } } } - } - - // KeepAliveProcess is controlled by AutoRegistrationProcess. - // All methods will be invoked in sync with SipService.this. - private class KeepAliveProcess implements Runnable { - private static final String TAG = "\\KEEPALIVE/"; - private static final int INTERVAL = 10; - private SipSessionGroup.SipSessionImpl mSession; - private boolean mRunning = false; - private int mInterval = INTERVAL; - - public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { - mSession = session; - } - - public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(INTERVAL * 1000, this); - } - private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh NAT port mapping " + duration + "s later."); - mTimer.cancel(this); - mTimer.set(duration * 1000, this); - } - - // timeout handler - public void run() { + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { synchronized (SipService.this) { - if (!mRunning) return; - - if (DEBUGV) Log.v(TAG, "~~~ keepalive: " - + mSession.getLocalProfile().getUriString()); - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); - try { - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - // Acquire wake lock for the registration process. The - // lock will be released when registration is complete. - mMyWakeLock.acquire(mSession); - mSession.register(EXPIRY_TIME); + if (!portChanged) { + if (++mPassCounter != PASS_THRESHOLD) return; + // update the interval, since the current interval is good to + // keep the port mapping. + mKeepAliveInterval = mMinInterval = mInterval; + if (DEBUG) { + Log.d(TAG, "measured good keepalive interval: " + + mKeepAliveInterval); } - if (mKeepAliveInterval > mInterval) { - mInterval = mKeepAliveInterval; - restart(mInterval); + onKeepAliveIntervalChanged(); + } else { + // Since the rport is changed, shorten the interval. + mMaxInterval = mInterval; + } + if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { + // update mKeepAliveInterval and stop measurement. + stop(); + mKeepAliveInterval = mMinInterval; + if (DEBUG) { + Log.d(TAG, "measured keepalive interval: " + + mKeepAliveInterval); } - } catch (Throwable t) { - Log.w(TAG, "keepalive error: " + t); + } else { + // calculate the new interval and continue. + mInterval = (mMaxInterval + mMinInterval) / 2; + mPassCounter = 0; + if (DEBUG) { + Log.d(TAG, "current interval: " + mKeepAliveInterval + + ", test new interval: " + mInterval); + } + restart(); } } } - public void stop() { - if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:" - + mSession.getLocalProfile().getUriString()); - mRunning = false; - mSession = null; - mTimer.cancel(this); + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + synchronized (SipService.this) { + Log.w(TAG, "interval measurement error: " + description); + } } } private class AutoRegistrationProcess extends SipSessionAdapter - implements Runnable { + implements Runnable, SipSessionGroup.KeepAliveProcessCallback { + private String TAG = "SipAudoReg"; private SipSessionGroup.SipSessionImpl mSession; + private SipSessionGroup.SipSessionImpl mKeepAliveSession; private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private KeepAliveProcess mKeepAliveProcess; private int mBackoff = 1; private boolean mRegistered; private long mExpiryTime; @@ -869,27 +871,38 @@ public final class SipService extends ISipService.Stub { // return right away if no active network connection. if (mSession == null) return; - synchronized (SipService.this) { - if (isBehindNAT(mLocalIp) - && (mIntervalMeasurementProcess == null) - && (mKeepAliveInterval == -1)) { - // Start keep-alive interval measurement, here we allow - // the first profile only as the target service provider - // to measure the life time of NAT port mapping. - startPortMappingLifetimeMeasurement(group); - } - } - // start unregistration to clear up old registration at server // TODO: when rfc5626 is deployed, use reg-id and sip.instance // in registration to avoid adding duplicate entries to server mMyWakeLock.acquire(mSession); mSession.unregister(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " - + mSession.getLocalProfile().getUriString()); + if (DEBUG) TAG = mSession.getLocalProfile().getUriString(); + if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess"); + } + } + + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { + synchronized (SipService.this) { + // Start keep-alive interval measurement on the first successfully + // kept-alive SipSessionGroup + startPortMappingLifetimeMeasurement(mSession.getLocalProfile()); + + if (!mRunning || !portChanged) return; + // Acquire wake lock for the registration process. The + // lock will be released when registration is complete. + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); } } + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + Log.e(TAG, "keepalive error: " + description); + } + public void stop() { if (!mRunning) return; mRunning = false; @@ -900,15 +913,30 @@ public final class SipService extends ISipService.Stub { } mTimer.cancel(this); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + mKeepAliveSession = null; } mRegistered = false; setListener(mProxy.getListener()); } + public void onKeepAliveIntervalChanged() { + if (mKeepAliveSession != null) { + int newInterval = getKeepAliveInterval(); + if (DEBUGV) { + Log.v(TAG, "restart keepalive w interval=" + newInterval); + } + mKeepAliveSession.stopKeepAliveProcess(); + try { + mKeepAliveSession.startKeepAliveProcess(newInterval, this); + } catch (SipException e) { + Log.e(TAG, "onKeepAliveIntervalChanged()", e); + } + } + } + public void setListener(ISipSessionListener listener) { synchronized (SipService.this) { mProxy.setListener(listener); @@ -955,13 +983,14 @@ public final class SipService extends ISipService.Stub { } // timeout handler: re-register + @Override public void run() { synchronized (SipService.this) { if (!mRunning) return; mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); + if (DEBUG) Log.d(TAG, "registering"); if (mConnected) { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); @@ -969,22 +998,6 @@ public final class SipService extends ISipService.Stub { } } - private boolean isBehindNAT(String address) { - try { - byte[] d = InetAddress.getByName(address).getAddress(); - if ((d[0] == 10) || - (((0x000000FF & ((int)d[0])) == 172) && - ((0x000000F0 & ((int)d[1])) == 16)) || - (((0x000000FF & ((int)d[0])) == 192) && - ((0x000000FF & ((int)d[1])) == 168))) { - return true; - } - } catch (UnknownHostException e) { - Log.e(TAG, "isBehindAT()" + address, e); - } - return false; - } - private void restart(int duration) { if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); mTimer.cancel(this); @@ -1030,7 +1043,6 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationDone(session, duration); if (duration > 0) { - mSession.clearReRegisterRequired(); mExpiryTime = SystemClock.elapsedRealtime() + (duration * 1000); @@ -1043,13 +1055,17 @@ public final class SipService extends ISipService.Stub { } restart(duration); - if (isBehindNAT(mLocalIp) || - mSession.getLocalProfile().getSendKeepAlive()) { - if (mKeepAliveProcess == null) { - mKeepAliveProcess = - new KeepAliveProcess(mSession); + SipProfile localProfile = mSession.getLocalProfile(); + if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) + || localProfile.getSendKeepAlive())) { + mKeepAliveSession = mSession.duplicate(); + Log.d(TAG, "start keepalive"); + try { + mKeepAliveSession.startKeepAliveProcess( + getKeepAliveInterval(), this); + } catch (SipException e) { + Log.e(TAG, "AutoRegistrationProcess", e); } - mKeepAliveProcess.start(); } } mMyWakeLock.release(session); @@ -1103,10 +1119,6 @@ public final class SipService extends ISipService.Stub { private void restartLater() { mRegistered = false; restart(backoffDuration()); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 4837eb9..cc3e410 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -28,6 +28,7 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipErrorCode; import android.net.sip.SipProfile; import android.net.sip.SipSession; +import android.net.sip.SipSessionAdapter; import android.text.TextUtils; import android.util.Log; @@ -89,6 +90,7 @@ class SipSessionGroup implements SipListener { private static final String THREAD_POOL_SIZE = "1"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds + private static final int KEEPALIVE_TIMEOUT = 3; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds private static final EventObject DEREGISTER = new EventObject("Deregister"); @@ -107,6 +109,7 @@ class SipSessionGroup implements SipListener { private SipSessionImpl mCallReceiverSession; private String mLocalIp; + private SipWakeupTimer mWakeupTimer; private SipWakeLock mWakeLock; // call-id-to-SipSession map @@ -119,13 +122,21 @@ class SipSessionGroup implements SipListener { * @throws IOException if cannot assign requested address */ public SipSessionGroup(String localIp, SipProfile myself, String password, - SipWakeLock wakeLock) throws SipException, IOException { + SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, + IOException { mLocalProfile = myself; mPassword = password; + mWakeupTimer = timer; mWakeLock = wakeLock; reset(localIp); } + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mWakeupTimer = timer; + } + synchronized void reset(String localIp) throws SipException, IOException { mLocalIp = localIp; if (localIp == null) return; @@ -382,6 +393,12 @@ class SipSessionGroup implements SipListener { } } + static interface KeepAliveProcessCallback { + /** Invoked when the response of keeping alive comes back. */ + void onResponse(boolean portChanged); + void onError(int errorCode, String description); + } + class SipSessionImpl extends ISipSession.Stub { SipProfile mPeerProfile; SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); @@ -392,12 +409,10 @@ class SipSessionGroup implements SipListener { ClientTransaction mClientTransaction; String mPeerSessionDescription; boolean mInCall; - SessionTimer mTimer; + SessionTimer mSessionTimer; int mAuthenticationRetryCount; - // for registration - boolean mReRegisterFlag = false; - int mRPort = 0; + private KeepAliveProcess mKeepAliveProcess; // lightweight timer class SessionTimer { @@ -512,7 +527,9 @@ class SipSessionGroup implements SipListener { try { processCommand(command); } catch (Throwable e) { - Log.w(TAG, "command error: " + command, e); + Log.w(TAG, "command error: " + command + ": " + + mLocalProfile.getUriString(), + getRootCause(e)); onError(e); } } @@ -553,34 +570,6 @@ class SipSessionGroup implements SipListener { doCommandAsync(DEREGISTER); } - public boolean isReRegisterRequired() { - return mReRegisterFlag; - } - - public void clearReRegisterRequired() { - mReRegisterFlag = false; - } - - public void sendKeepAlive() { - mState = SipSession.State.PINGING; - try { - processCommand(new OptionsCommand()); - for (int i = 0; i < 15; i++) { - if (SipSession.State.PINGING != mState) break; - Thread.sleep(200); - } - if (SipSession.State.PINGING == mState) { - // FIXME: what to do if server doesn't respond - reset(); - if (DEBUG) Log.w(TAG, "no response from ping"); - } - } catch (SipException e) { - Log.e(TAG, "sendKeepAlive failed", e); - } catch (InterruptedException e) { - Log.e(TAG, "sendKeepAlive interrupted", e); - } - } - private void processCommand(EventObject command) throws SipException { if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); if (!process(command)) { @@ -612,6 +601,11 @@ class SipSessionGroup implements SipListener { synchronized (SipSessionGroup.this) { if (isClosed()) return false; + if (mKeepAliveProcess != null) { + // event consumed by keepalive process + if (mKeepAliveProcess.process(evt)) return true; + } + Dialog dialog = null; if (evt instanceof RequestEvent) { dialog = ((RequestEvent) evt).getDialog(); @@ -627,9 +621,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.DEREGISTERING: processed = registeringToReady(evt); break; - case SipSession.State.PINGING: - processed = keepAliveProcess(evt); - break; case SipSession.State.READY_TO_CALL: processed = readyForCall(evt); break; @@ -754,10 +745,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.OUTGOING_CALL_CANCELING: onError(SipErrorCode.TIME_OUT, event.toString()); break; - case SipSession.State.PINGING: - reset(); - mReRegisterFlag = true; - break; default: Log.d(TAG, " do nothing"); @@ -778,48 +765,6 @@ class SipSessionGroup implements SipListener { return expires; } - private boolean keepAliveProcess(EventObject evt) throws SipException { - if (evt instanceof OptionsCommand) { - mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - return true; - } else if (evt instanceof ResponseEvent) { - return parseOptionsResult(evt); - } - return false; - } - - private boolean parseOptionsResult(EventObject evt) { - if (expectResponse(Request.OPTIONS, evt)) { - ResponseEvent event = (ResponseEvent) evt; - int rPort = getRPortFromResponse(event.getResponse()); - if (rPort != -1) { - if (mRPort == 0) mRPort = rPort; - if (mRPort != rPort) { - mReRegisterFlag = true; - if (DEBUG) Log.w(TAG, String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); - } - } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); - } - reset(); - return true; - } - return false; - } - - private int getRPortFromResponse(Response response) { - ViaHeader viaHeader = (ViaHeader)(response.getHeader( - SIPHeaderNames.VIA)); - return (viaHeader == null) ? -1 : viaHeader.getRPort(); - } - private boolean registeringToReady(EventObject evt) throws SipException { if (expectResponse(Request.REGISTER, evt)) { @@ -1138,15 +1083,15 @@ class SipSessionGroup implements SipListener { // timeout in seconds private void startSessionTimer(int timeout) { if (timeout > 0) { - mTimer = new SessionTimer(); - mTimer.start(timeout); + mSessionTimer = new SessionTimer(); + mSessionTimer.start(timeout); } } private void cancelSessionTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; + if (mSessionTimer != null) { + mSessionTimer.cancel(); + mSessionTimer = null; } } @@ -1272,6 +1217,168 @@ class SipSessionGroup implements SipListener { onRegistrationFailed(getErrorCode(statusCode), createErrorMessage(response)); } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + startKeepAliveProcess(interval, mLocalProfile, callback); + } + } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, SipProfile peerProfile, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + throw new SipException("Cannot create more than one " + + "keepalive process in a SipSession"); + } + mPeerProfile = peerProfile; + mKeepAliveProcess = new KeepAliveProcess(); + mProxy.setListener(mKeepAliveProcess); + mKeepAliveProcess.start(interval, callback); + } + } + + public void stopKeepAliveProcess() { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + } + } + + class KeepAliveProcess extends SipSessionAdapter implements Runnable { + private static final String TAG = "SipKeepAlive"; + private boolean mRunning = false; + private KeepAliveProcessCallback mCallback; + + private boolean mPortChanged = false; + private int mRPort = 0; + + // @param interval in seconds + void start(int interval, KeepAliveProcessCallback callback) { + if (mRunning) return; + mRunning = true; + mCallback = new KeepAliveProcessCallbackProxy(callback); + mWakeupTimer.set(interval * 1000, this); + if (DEBUG) { + Log.d(TAG, "start keepalive:" + + mLocalProfile.getUriString()); + } + + // No need to run the first time in a separate thread for now + run(); + } + + // return true if the event is consumed + boolean process(EventObject evt) throws SipException { + if (mRunning && (mState == SipSession.State.PINGING)) { + if (evt instanceof ResponseEvent) { + if (parseOptionsResult(evt)) { + if (mPortChanged) { + stop(); + } else { + cancelSessionTimer(); + removeSipSession(SipSessionImpl.this); + } + mCallback.onResponse(mPortChanged); + return true; + } + } + } + return false; + } + + // SipSessionAdapter + // To react to the session timeout event and network error. + @Override + public void onError(ISipSession session, int errorCode, String message) { + stop(); + mCallback.onError(errorCode, message); + } + + // SipWakeupTimer timeout handler + // To send out keepalive message. + @Override + public void run() { + synchronized (SipSessionGroup.this) { + if (!mRunning) return; + + if (DEBUG_PING) { + Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() + + " --> " + mPeerProfile); + } + try { + sendKeepAlive(); + } catch (Throwable t) { + Log.w(TAG, "keepalive error: " + ": " + + mLocalProfile.getUriString(), getRootCause(t)); + // It's possible that the keepalive process is being stopped + // during session.sendKeepAlive() so need to check mRunning + // again here. + if (mRunning) SipSessionImpl.this.onError(t); + } + } + } + + void stop() { + synchronized (SipSessionGroup.this) { + if (DEBUG) { + Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString() + + ",RPort=" + mRPort); + } + mRunning = false; + mWakeupTimer.cancel(this); + reset(); + } + } + + private void sendKeepAlive() throws SipException, InterruptedException { + synchronized (SipSessionGroup.this) { + mState = SipSession.State.PINGING; + mClientTransaction = mSipHelper.sendOptions( + mLocalProfile, mPeerProfile, generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(SipSessionImpl.this); + + startSessionTimer(KEEPALIVE_TIMEOUT); + // when timed out, onError() will be called with SipErrorCode.TIME_OUT + } + } + + private boolean parseOptionsResult(EventObject evt) { + if (expectResponse(Request.OPTIONS, evt)) { + ResponseEvent event = (ResponseEvent) evt; + int rPort = getRPortFromResponse(event.getResponse()); + if (rPort != -1) { + if (mRPort == 0) mRPort = rPort; + if (mRPort != rPort) { + mPortChanged = true; + if (DEBUG) Log.d(TAG, String.format( + "rport is changed: %d <> %d", mRPort, rPort)); + mRPort = rPort; + } else { + if (DEBUG) Log.d(TAG, "rport is the same: " + rPort); + } + } else { + if (DEBUG) Log.w(TAG, "peer did not respond rport"); + } + return true; + } + return false; + } + + private int getRPortFromResponse(Response response) { + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + return (viaHeader == null) ? -1 : viaHeader.getRPort(); + } + } } /** @@ -1363,15 +1470,16 @@ class SipSessionGroup implements SipListener { if (!isLoggable(s)) return false; if (evt == null) return false; - if (evt instanceof OptionsCommand) { - return DEBUG_PING; - } else if (evt instanceof ResponseEvent) { + if (evt instanceof ResponseEvent) { Response response = ((ResponseEvent) evt).getResponse(); if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { return DEBUG_PING; } return DEBUG; } else if (evt instanceof RequestEvent) { + if (isRequestEvent(Request.OPTIONS, evt)) { + return DEBUG_PING; + } return DEBUG; } return false; @@ -1387,12 +1495,6 @@ class SipSessionGroup implements SipListener { } } - private class OptionsCommand extends EventObject { - public OptionsCommand() { - super(SipSessionGroup.this); - } - } - private class RegisterCommand extends EventObject { private int mDuration; @@ -1434,4 +1536,46 @@ class SipSessionGroup implements SipListener { return mTimeout; } } + + /** Class to help safely run KeepAliveProcessCallback in a different thread. */ + static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback { + private KeepAliveProcessCallback mCallback; + + KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) { + mCallback = callback; + } + + private void proxy(Runnable runnable) { + // 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, "SIP-KeepAliveProcessCallbackThread").start(); + } + + public void onResponse(final boolean portChanged) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onResponse(portChanged); + } catch (Throwable t) { + Log.w(TAG, "onResponse", t); + } + } + }); + } + + public void onError(final int errorCode, final String description) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onError(errorCode, description); + } catch (Throwable t) { + Log.w(TAG, "onError", t); + } + } + }); + } + } } -- cgit v1.2.3 From 9dbe2c72276b9968593054dcfd39fd5135a574aa Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 23 Jun 2011 18:23:09 +0800 Subject: Keep the keepalive process going after NAT port is changed. This is a regression from the CL that makes the keep-alive process a reusable component. Change-Id: I1d580588e9e303c532bf620056fc0fe88a2fdcda --- java/com/android/server/sip/SipService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 802e56d..f8e5b3a 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -890,6 +890,12 @@ public final class SipService extends ISipService.Stub { startPortMappingLifetimeMeasurement(mSession.getLocalProfile()); if (!mRunning || !portChanged) return; + + // The keep alive process is stopped when port is changed; + // Nullify the session so that the process can be restarted + // again when the re-registration is done + mKeepAliveSession = null; + // Acquire wake lock for the registration process. The // lock will be released when registration is complete. mMyWakeLock.acquire(mSession); -- cgit v1.2.3 From f22d0eb0b438a8354144e3abe087f8b13e62cd55 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 23 Jun 2011 18:32:59 +0800 Subject: Execute all the due wakeup events in SipWakeupTimer. Events are sorted by periods. So events of larger periods may have trigger time (i.e., when the event should be processed) earlier than the ones of smaller periods. So need to scan the whole queue looking for due events. The scan takes O(n) time but we expect the queue size to be small. Change-Id: I08bd3bd9d4bb8decb78f3c91c943396463ca023a --- java/com/android/server/sip/SipWakeupTimer.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java index 9cc26b0..76780c0 100644 --- a/java/com/android/server/sip/SipWakeupTimer.java +++ b/java/com/android/server/sip/SipWakeupTimer.java @@ -173,7 +173,7 @@ class SipWakeupTimer extends BroadcastReceiver { long triggerTime = event.mTriggerTime; if (DEBUG_TIMER) { - Log.d(TAG, " add event " + event + " scheduled at " + Log.d(TAG, " add event " + event + " scheduled on " + showTime(triggerTime) + " at " + showTime(now) + ", #events=" + mEventQueue.size()); printQueue(); @@ -267,10 +267,10 @@ class SipWakeupTimer extends BroadcastReceiver { if (stopped() || mEventQueue.isEmpty()) return; for (MyEvent event : mEventQueue) { - if (event.mTriggerTime != triggerTime) break; + if (event.mTriggerTime != triggerTime) continue; if (DEBUG_TIMER) Log.d(TAG, "execute " + event); - event.mLastTriggerTime = event.mTriggerTime; + event.mLastTriggerTime = triggerTime; event.mTriggerTime += event.mPeriod; // run the callback in the handler thread to prevent deadlock @@ -324,6 +324,8 @@ class SipWakeupTimer extends BroadcastReceiver { } } + // Sort the events by mMaxPeriod so that the first event can be used to + // align events with larger periods private static class MyEventComparator implements Comparator { public int compare(MyEvent e1, MyEvent e2) { if (e1 == e2) return 0; -- cgit v1.2.3 From 44ccfb03bb349a90546ddb3dc0063cbf4aaddaf1 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 24 Jun 2011 15:17:25 +0800 Subject: Restart NAT port timeout measurement when keepalive fails and other fixes Misc keepalive fixes including: + Restart NAT port timeout measurement when keepalive fails. The max interval is set to the current keepalive interval. + When exception occurs during sending a keepalive, restarts registration. + When exception occurs during measurement, retry for a limited times before giving up. Change-Id: I7aa787a5ec7c4c9b4334aa1017371d9049b3520c --- java/com/android/server/sip/SipService.java | 65 +++++++++++++++++++----- java/com/android/server/sip/SipSessionGroup.java | 4 +- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index f8e5b3a..3b0f546 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -462,17 +462,30 @@ public final class SipService extends ISipService.Stub { private void startPortMappingLifetimeMeasurement( SipProfile localProfile) { + startPortMappingLifetimeMeasurement(localProfile, -1); + } + + private void startPortMappingLifetimeMeasurement( + SipProfile localProfile, int maxInterval) { if ((mIntervalMeasurementProcess == null) && (mKeepAliveInterval == -1) && isBehindNAT(mLocalIp)) { Log.d(TAG, "start NAT port mapping timeout measurement on " + localProfile.getUriString()); - mIntervalMeasurementProcess = new IntervalMeasurementProcess(localProfile); + mIntervalMeasurementProcess = + new IntervalMeasurementProcess(localProfile, maxInterval); mIntervalMeasurementProcess.start(); } } + private void restartPortMappingLifetimeMeasurement( + SipProfile localProfile, int maxInterval) { + stopPortMappingMeasurement(); + mKeepAliveInterval = -1; + startPortMappingLifetimeMeasurement(localProfile, maxInterval); + } + private synchronized void addPendingSession(ISipSession session) { try { cleanUpPendingSessions(); @@ -746,18 +759,30 @@ public final class SipService extends ISipService.Stub { private class IntervalMeasurementProcess implements SipSessionGroup.KeepAliveProcessCallback { private static final String TAG = "SipKeepAliveInterval"; - private static final int MAX_INTERVAL = 120; // seconds - private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME; + private static final int MAX_INTERVAL = 120; // in seconds + private static final int MIN_INTERVAL = 10; // in seconds private static final int PASS_THRESHOLD = 10; + private static final int MAX_RETRY_COUNT = 5; private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; private boolean mRunning; private int mMinInterval = 10; // in seconds - private int mMaxInterval = MAX_INTERVAL; - private int mInterval = MAX_INTERVAL / 2; - private int mPassCounter = 0; + private int mMaxInterval; + private int mInterval; + private int mPassCount = 0; + private int mErrorCount = 0; + + public IntervalMeasurementProcess(SipProfile localProfile, int maxInterval) { + mMaxInterval = (maxInterval < 0) ? MAX_INTERVAL : maxInterval; + mInterval = (mMaxInterval + mMinInterval) / 2; + + // Don't start measurement if the interval is too small + if (mInterval < MIN_INTERVAL) { + Log.w(TAG, "interval is too small; measurement aborted; " + + "maxInterval=" + mMaxInterval); + return; + } - public IntervalMeasurementProcess(SipProfile localProfile) { try { mGroup = new SipSessionGroupExt(localProfile, null, null); // TODO: remove this line once SipWakeupTimer can better handle @@ -801,8 +826,10 @@ public final class SipService extends ISipService.Stub { @Override public void onResponse(boolean portChanged) { synchronized (SipService.this) { + mErrorCount = 0; + if (!portChanged) { - if (++mPassCounter != PASS_THRESHOLD) return; + if (++mPassCount != PASS_THRESHOLD) return; // update the interval, since the current interval is good to // keep the port mapping. mKeepAliveInterval = mMinInterval = mInterval; @@ -826,7 +853,7 @@ public final class SipService extends ISipService.Stub { } else { // calculate the new interval and continue. mInterval = (mMaxInterval + mMinInterval) / 2; - mPassCounter = 0; + mPassCount = 0; if (DEBUG) { Log.d(TAG, "current interval: " + mKeepAliveInterval + ", test new interval: " + mInterval); @@ -841,6 +868,13 @@ public final class SipService extends ISipService.Stub { public void onError(int errorCode, String description) { synchronized (SipService.this) { Log.w(TAG, "interval measurement error: " + description); + if (++mErrorCount < MAX_RETRY_COUNT) { + Log.w(TAG, " retry count = " + mErrorCount); + mPassCount = 0; + restart(); + } else { + Log.w(TAG, " max retry count reached; measurement aborted"); + } } } } @@ -885,9 +919,15 @@ public final class SipService extends ISipService.Stub { @Override public void onResponse(boolean portChanged) { synchronized (SipService.this) { - // Start keep-alive interval measurement on the first successfully - // kept-alive SipSessionGroup - startPortMappingLifetimeMeasurement(mSession.getLocalProfile()); + if (portChanged) { + restartPortMappingLifetimeMeasurement( + mSession.getLocalProfile(), getKeepAliveInterval()); + } else { + // Start keep-alive interval measurement on the first + // successfully kept-alive SipSessionGroup + startPortMappingLifetimeMeasurement( + mSession.getLocalProfile()); + } if (!mRunning || !portChanged) return; @@ -907,6 +947,7 @@ public final class SipService extends ISipService.Stub { @Override public void onError(int errorCode, String description) { Log.e(TAG, "keepalive error: " + description); + onResponse(true); // re-register immediately } public void stop() { diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index cc3e410..2d0dd9c 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1259,11 +1259,13 @@ class SipSessionGroup implements SipListener { private boolean mPortChanged = false; private int mRPort = 0; + private int mInterval; // just for debugging // @param interval in seconds void start(int interval, KeepAliveProcessCallback callback) { if (mRunning) return; mRunning = true; + mInterval = interval; mCallback = new KeepAliveProcessCallbackProxy(callback); mWakeupTimer.set(interval * 1000, this); if (DEBUG) { @@ -1311,7 +1313,7 @@ class SipSessionGroup implements SipListener { if (DEBUG_PING) { Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() - + " --> " + mPeerProfile); + + " --> " + mPeerProfile + ", interval=" + mInterval); } try { sendKeepAlive(); -- cgit v1.2.3 From 22ecc3df834674605daf86f7edf20169b6ca800b Mon Sep 17 00:00:00 2001 From: repo sync Date: Thu, 23 Jun 2011 19:40:36 +0800 Subject: Support Invite w/ Replaces request. bug:3326870 Change-Id: Idbfbe7e3cc6ba83874d42bfb7d149866f454e70a --- java/android/net/sip/ISipSessionListener.aidl | 8 ++ java/android/net/sip/SipAudioCall.java | 57 +++++++++++++- java/android/net/sip/SipSession.java | 21 +++++ java/android/net/sip/SipSessionAdapter.java | 4 + java/com/android/server/sip/SipHelper.java | 2 +- java/com/android/server/sip/SipSessionGroup.java | 89 ++++++++++++++++++---- .../server/sip/SipSessionListenerProxy.java | 14 ++++ 7 files changed, 180 insertions(+), 15 deletions(-) diff --git a/java/android/net/sip/ISipSessionListener.aidl b/java/android/net/sip/ISipSessionListener.aidl index 5920bca..690700c 100644 --- a/java/android/net/sip/ISipSessionListener.aidl +++ b/java/android/net/sip/ISipSessionListener.aidl @@ -71,6 +71,14 @@ interface ISipSessionListener { */ void onCallBusy(in ISipSession session); + /** + * Called when the call is being transferred to a new one. + * + * @param newSession the new session that the call will be transferred to + * @param sessionDescription the new peer's session description + */ + void onCallTransferring(in ISipSession newSession, String sessionDescription); + /** * Called when an error occurs during session initialization and * termination. diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index b46f826..2666b69 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -170,6 +170,7 @@ public class SipAudioCall { private SipProfile mLocalProfile; private SipAudioCall.Listener mListener; private SipSession mSipSession; + private SipSession mTransferringSession; private long mSessionId = System.currentTimeMillis(); private String mPeerSd; @@ -347,6 +348,27 @@ public class SipAudioCall { } } + private synchronized void transferToNewSession() { + if (mTransferringSession == null) return; + SipSession origin = mSipSession; + mSipSession = mTransferringSession; + mTransferringSession = null; + + // stop the replaced call. + if (mAudioStream != null) { + mAudioStream.join(null); + } else { + try { + mAudioStream = new AudioStream(InetAddress.getByName( + getLocalIp())); + } catch (Throwable t) { + Log.i(TAG, "transferToNewSession(): " + t); + } + } + if (origin != null) origin.endCall(); + startAudio(); + } + private SipSession.Listener createListener() { return new SipSession.Listener() { @Override @@ -404,6 +426,13 @@ public class SipAudioCall { mPeerSd = sessionDescription; Log.v(TAG, "onCallEstablished()" + mPeerSd); + // TODO: how to notify the UI that the remote party is changed + if ((mTransferringSession != null) + && (session == mTransferringSession)) { + transferToNewSession(); + return; + } + Listener listener = mListener; if (listener != null) { try { @@ -420,7 +449,17 @@ public class SipAudioCall { @Override public void onCallEnded(SipSession session) { - Log.d(TAG, "sip call ended: " + session); + Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession); + // reset the trasnferring session if it is the one. + if (session == mTransferringSession) { + mTransferringSession = null; + return; + } + // or ignore the event if the original session is being + // transferred to the new one. + if ((mTransferringSession != null) || + (session != mSipSession)) return; + Listener listener = mListener; if (listener != null) { try { @@ -489,6 +528,22 @@ public class SipAudioCall { public void onRegistrationDone(SipSession session, int duration) { // irrelevant } + + @Override + public void onCallTransferring(SipSession newSession, + String sessionDescription) { + Log.v(TAG, "onCallTransferring mSipSession:" + + mSipSession + " newSession:" + newSession); + mTransferringSession = newSession; + // session changing request + try { + String answer = createAnswer(sessionDescription).encode(); + newSession.answerCall(answer, SESSION_TIMEOUT); + } catch (Throwable e) { + Log.e(TAG, "onCallTransferring()", e); + newSession.endCall(); + } + } }; } diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java index 5629b3c..5ba1626 100644 --- a/java/android/net/sip/SipSession.java +++ b/java/android/net/sip/SipSession.java @@ -159,6 +159,17 @@ public final class SipSession { public void onCallBusy(SipSession session) { } + /** + * Called when the call is being transferred to a new one. + * + * @hide + * @param newSession the new session that the call will be transferred to + * @param sessionDescription the new peer's session description + */ + public void onCallTransferring(SipSession newSession, + String sessionDescription) { + } + /** * Called when an error occurs during session initialization and * termination. @@ -489,6 +500,16 @@ public final class SipSession { } } + public void onCallTransferring(ISipSession session, + String sessionDescription) { + if (mListener != null) { + mListener.onCallTransferring( + new SipSession(session, SipSession.this.mListener), + sessionDescription); + + } + } + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { if (mListener != null) { diff --git a/java/android/net/sip/SipSessionAdapter.java b/java/android/net/sip/SipSessionAdapter.java index 86aca37..f538983 100644 --- a/java/android/net/sip/SipSessionAdapter.java +++ b/java/android/net/sip/SipSessionAdapter.java @@ -42,6 +42,10 @@ public class SipSessionAdapter extends ISipSessionListener.Stub { public void onCallBusy(ISipSession session) { } + public void onCallTransferring(ISipSession session, + String sessionDescription) { + } + public void onCallChangeFailed(ISipSession session, int errorCode, String message) { } diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 018e6de..47950e3 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -314,7 +314,7 @@ class SipHelper { } } - private ServerTransaction getServerTransaction(RequestEvent event) + public ServerTransaction getServerTransaction(RequestEvent event) throws SipException { ServerTransaction transaction = event.getServerTransaction(); if (transaction == null) { diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 2d0dd9c..481e306 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -21,6 +21,8 @@ import gov.nist.javax.sip.clientauthutils.UserCredentials; import gov.nist.javax.sip.header.SIPHeaderNames; import gov.nist.javax.sip.header.ProxyAuthenticate; import gov.nist.javax.sip.header.WWWAuthenticate; +import gov.nist.javax.sip.header.extensions.ReferredByHeader; +import gov.nist.javax.sip.header.extensions.ReplacesHeader; import gov.nist.javax.sip.message.SIPMessage; import android.net.sip.ISipSession; @@ -365,24 +367,85 @@ class SipSessionGroup implements SipListener { super(listener); } + private SipSessionImpl createNewSession(RequestEvent event, + ISipSessionListener listener, ServerTransaction transaction) + throws SipException { + SipSessionImpl newSession = new SipSessionImpl(listener); + newSession.mServerTransaction = transaction; + newSession.mState = SipSession.State.INCOMING_CALL; + newSession.mDialog = newSession.mServerTransaction.getDialog(); + newSession.mInviteReceived = event; + newSession.mPeerProfile = createPeerProfile(event.getRequest()); + newSession.mPeerSessionDescription = + extractContent(event.getRequest()); + return newSession; + } + + private int processInviteWithReplaces(RequestEvent event, + ReplacesHeader replaces) { + String callId = replaces.getCallId(); + SipSessionImpl session = mSessionMap.get(callId); + if (session == null) { + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + + Dialog dialog = session.mDialog; + if (dialog == null) return Response.DECLINE; + + if (!dialog.getLocalTag().equals(replaces.getToTag()) || + !dialog.getRemoteTag().equals(replaces.getFromTag())) { + // No match is found, returns 481. + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + + ReferredByHeader referredBy = (ReferredByHeader) event.getRequest() + .getHeader(ReferredByHeader.NAME); + if ((referredBy == null) || + !dialog.getRemoteParty().equals(referredBy.getAddress())) { + return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST; + } + return Response.OK; + } + + private void processNewInviteRequest(RequestEvent event) + throws SipException { + ReplacesHeader replaces = (ReplacesHeader) event.getRequest() + .getHeader(ReplacesHeader.NAME); + SipSessionImpl newSession = null; + if (replaces != null) { + int response = processInviteWithReplaces(event, replaces); + if (DEBUG) { + Log.v(TAG, "ReplacesHeader: " + replaces + + " response=" + response); + } + if (response == Response.OK) { + SipSessionImpl replacedSession = + mSessionMap.get(replaces.getCallId()); + // got INVITE w/ replaces request. + newSession = createNewSession(event, + replacedSession.mProxy.getListener(), + mSipHelper.getServerTransaction(event)); + newSession.mProxy.onCallTransferring(newSession, + newSession.mPeerSessionDescription); + } else { + mSipHelper.sendResponse(event, response); + } + } else { + // New Incoming call. + newSession = createNewSession(event, mProxy, + mSipHelper.sendRinging(event, generateTag())); + mProxy.onRinging(newSession, newSession.mPeerProfile, + newSession.mPeerSessionDescription); + } + if (newSession != null) addSipSession(newSession); + } + public boolean process(EventObject evt) throws SipException { if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + SipSession.State.toString(mState) + ": processing " + log(evt)); if (isRequestEvent(Request.INVITE, evt)) { - RequestEvent event = (RequestEvent) evt; - SipSessionImpl newSession = new SipSessionImpl(mProxy); - newSession.mState = SipSession.State.INCOMING_CALL; - newSession.mServerTransaction = mSipHelper.sendRinging(event, - generateTag()); - newSession.mDialog = newSession.mServerTransaction.getDialog(); - newSession.mInviteReceived = event; - newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mPeerSessionDescription = - extractContent(event.getRequest()); - addSipSession(newSession); - mProxy.onRinging(newSession, newSession.mPeerProfile, - newSession.mPeerSessionDescription); + processNewInviteRequest((RequestEvent) evt); return true; } else if (isRequestEvent(Request.OPTIONS, evt)) { mSipHelper.sendResponse((RequestEvent) evt, Response.OK); diff --git a/java/com/android/server/sip/SipSessionListenerProxy.java b/java/com/android/server/sip/SipSessionListenerProxy.java index f8be0a8..8655a3a 100644 --- a/java/com/android/server/sip/SipSessionListenerProxy.java +++ b/java/com/android/server/sip/SipSessionListenerProxy.java @@ -110,6 +110,20 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + public void onCallTransferring(final ISipSession newSession, + final String sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallTransferring(newSession, sessionDescription); + } catch (Throwable t) { + handle(t, "onCallTransferring()"); + } + } + }); + } + public void onCallBusy(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { -- cgit v1.2.3 From 0f8ceff66069c98480481241ba9d70dc34448188 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 27 Jun 2011 19:20:48 +0800 Subject: Start keepalive process for the caller of a SIP call so that the callee can send signals (on-hold or bye) back to the caller. Without the keepalive, the NAT port for the caller will be timed out during the call. And the signals will be dropped by the NAT device. Change-Id: I21848d73469045b2ed9e7281556ab184c594c362 --- java/com/android/server/sip/SipSessionGroup.java | 30 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 481e306..6304369 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -93,6 +93,7 @@ class SipSessionGroup implements SipListener { private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds private static final int KEEPALIVE_TIMEOUT = 3; // in seconds + private static final int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds private static final EventObject DEREGISTER = new EventObject("Deregister"); @@ -477,6 +478,8 @@ class SipSessionGroup implements SipListener { private KeepAliveProcess mKeepAliveProcess; + private SipSessionImpl mKeepAliveSession; + // lightweight timer class SessionTimer { private boolean mRunning = true; @@ -545,6 +548,11 @@ class SipSessionGroup implements SipListener { mClientTransaction = null; cancelSessionTimer(); + + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + mKeepAliveSession = null; + } } public boolean isInCall() { @@ -999,7 +1007,7 @@ class SipSessionGroup implements SipListener { throws SipException { // expect ACK, CANCEL request if (isRequestEvent(Request.ACK, evt)) { - establishCall(); + establishCall(false); return true; } else if (isRequestEvent(Request.CANCEL, evt)) { // http://tools.ietf.org/html/rfc3261#section-9.2 @@ -1031,7 +1039,7 @@ class SipSessionGroup implements SipListener { case Response.OK: mSipHelper.sendInviteAck(event, mDialog); mPeerSessionDescription = extractContent(response); - establishCall(); + establishCall(true); return true; case Response.UNAUTHORIZED: case Response.PROXY_AUTHENTICATION_REQUIRED: @@ -1163,10 +1171,26 @@ class SipSessionGroup implements SipListener { response.getStatusCode()); } - private void establishCall() { + private void enableKeepAlive() { + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + } else { + mKeepAliveSession = duplicate(); + } + try { + mKeepAliveSession.startKeepAliveProcess( + INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null); + } catch (SipException e) { + Log.w(TAG, "keepalive cannot be enabled; ignored", e); + mKeepAliveSession.stopKeepAliveProcess(); + } + } + + private void establishCall(boolean enableKeepAlive) { mState = SipSession.State.IN_CALL; mInCall = true; cancelSessionTimer(); + if (enableKeepAlive) enableKeepAlive(); mProxy.onCallEstablished(this, mPeerSessionDescription); } -- cgit v1.2.3 From bb18b405c539e483cce67ae207bd7e6263c0d071 Mon Sep 17 00:00:00 2001 From: repo sync Date: Tue, 28 Jun 2011 15:25:44 +0800 Subject: Support INVITE w/o SDP. bug:3326873 Change-Id: Ie29d2c61b237fee2d8637f4ba3d293a22469cced --- java/android/net/sip/SipAudioCall.java | 3 +++ java/com/android/server/sip/SipSessionGroup.java | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 2666b69..c1affa6 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -26,6 +26,7 @@ import android.net.sip.SimpleSessionDescription.Media; import android.net.wifi.WifiManager; import android.os.Message; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import java.io.IOException; @@ -400,6 +401,7 @@ public class SipAudioCall { @Override public void onRinging(SipSession session, SipProfile peerProfile, String sessionDescription) { + // this callback is triggered only for reinvite. synchronized (SipAudioCall.this) { if ((mSipSession == null) || !mInCall || !session.getCallId().equals( @@ -730,6 +732,7 @@ public class SipAudioCall { } private SimpleSessionDescription createAnswer(String offerSd) { + if (TextUtils.isEmpty(offerSd)) return createOffer(); SimpleSessionDescription offer = new SimpleSessionDescription(offerSd); SimpleSessionDescription answer = diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 6304369..c0c1b28 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1007,7 +1007,13 @@ class SipSessionGroup implements SipListener { throws SipException { // expect ACK, CANCEL request if (isRequestEvent(Request.ACK, evt)) { - establishCall(false); + String sdp = extractContent(((RequestEvent) evt).getRequest()); + if (sdp != null) mPeerSessionDescription = sdp; + if (mPeerSessionDescription == null) { + onError(SipErrorCode.CLIENT_ERROR, "peer sdp is empty"); + } else { + establishCall(false); + } return true; } else if (isRequestEvent(Request.CANCEL, evt)) { // http://tools.ietf.org/html/rfc3261#section-9.2 -- cgit v1.2.3 From 3efff6c5840a99faadc3ee6197940c3290f65a62 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Tue, 28 Jun 2011 19:56:19 +0800 Subject: Record external IP and port from SIP responses and use them to create the contact header when sending OK response for INVITE. Bug: 3461707 Change-Id: I5b254618f4920cf10a1460631bcd336778f344ec --- java/com/android/server/sip/SipHelper.java | 30 ++++++++++++++----- java/com/android/server/sip/SipSessionGroup.java | 37 ++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 47950e3..c031bc1 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -150,9 +150,17 @@ class SipHelper { private ContactHeader createContactHeader(SipProfile profile) throws ParseException, SipException { - ListeningPoint lp = getListeningPoint(); - SipURI contactURI = - createSipUri(profile.getUserName(), profile.getProtocol(), lp); + return createContactHeader(profile, null, 0); + } + + private ContactHeader createContactHeader(SipProfile profile, + String ip, int port) throws ParseException, + SipException { + SipURI contactURI = (ip == null) + ? createSipUri(profile.getUserName(), profile.getProtocol(), + getListeningPoint()) + : createSipUri(profile.getUserName(), profile.getProtocol(), + ip, port); Address contactAddress = mAddressFactory.createAddress(contactURI); contactAddress.setDisplayName(profile.getDisplayName()); @@ -168,9 +176,14 @@ class SipHelper { private SipURI createSipUri(String username, String transport, ListeningPoint lp) throws ParseException { - SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress()); + return createSipUri(username, transport, lp.getIPAddress(), lp.getPort()); + } + + private SipURI createSipUri(String username, String transport, + String ip, int port) throws ParseException { + SipURI uri = mAddressFactory.createSipURI(username, ip); try { - uri.setPort(lp.getPort()); + uri.setPort(port); uri.setTransportParam(transport); } catch (InvalidArgumentException e) { throw new RuntimeException(e); @@ -353,13 +366,14 @@ class SipHelper { */ public ServerTransaction sendInviteOk(RequestEvent event, SipProfile localProfile, String sessionDescription, - ServerTransaction inviteTransaction) - throws SipException { + ServerTransaction inviteTransaction, String externalIp, + int externalPort) throws SipException { try { Request request = event.getRequest(); Response response = mMessageFactory.createResponse(Response.OK, request); - response.addHeader(createContactHeader(localProfile)); + response.addHeader(createContactHeader(localProfile, externalIp, + externalPort)); response.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index c0c1b28..047eb8d 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -119,6 +119,10 @@ class SipSessionGroup implements SipListener { private Map mSessionMap = new HashMap(); + // external address observed from any response + private String mExternalIp; + private int mExternalPort; + /** * @param myself the local profile with password crossed out * @param password the password of the profile @@ -175,6 +179,8 @@ class SipSessionGroup implements SipListener { mCallReceiverSession = null; mSessionMap.clear(); + + resetExternalAddress(); } synchronized void onConnectivityChanged() { @@ -190,6 +196,12 @@ class SipSessionGroup implements SipListener { } } + synchronized void resetExternalAddress() { + Log.d(TAG, " reset external addr on " + mSipStack); + mExternalIp = null; + mExternalPort = 0; + } + public SipProfile getLocalProfile() { return mLocalProfile; } @@ -363,6 +375,21 @@ class SipSessionGroup implements SipListener { return null; } + private void extractExternalAddress(ResponseEvent evt) { + Response response = evt.getResponse(); + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + if (viaHeader == null) return; + int rport = viaHeader.getRPort(); + String externalIp = viaHeader.getReceived(); + if ((rport > 0) && (externalIp != null)) { + mExternalIp = externalIp; + mExternalPort = rport; + Log.d(TAG, " got external addr " + externalIp + ":" + rport + + " on " + mSipStack); + } + } + private class SipSessionCallReceiverImpl extends SipSessionImpl { public SipSessionCallReceiverImpl(ISipSessionListener listener) { super(listener); @@ -682,6 +709,7 @@ class SipSessionGroup implements SipListener { dialog = ((RequestEvent) evt).getDialog(); } else if (evt instanceof ResponseEvent) { dialog = ((ResponseEvent) evt).getDialog(); + extractExternalAddress((ResponseEvent) evt); } if (dialog != null) mDialog = dialog; @@ -984,7 +1012,8 @@ class SipSessionGroup implements SipListener { mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile, ((MakeCallCommand) evt).getSessionDescription(), - mServerTransaction); + mServerTransaction, + mExternalIp, mExternalPort); startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } else if (END_CALL == evt) { @@ -1376,6 +1405,7 @@ class SipSessionGroup implements SipListener { if (evt instanceof ResponseEvent) { if (parseOptionsResult(evt)) { if (mPortChanged) { + resetExternalAddress(); stop(); } else { cancelSessionTimer(); @@ -1405,8 +1435,11 @@ class SipSessionGroup implements SipListener { if (!mRunning) return; if (DEBUG_PING) { + String peerUri = (mPeerProfile == null) + ? "null" + : mPeerProfile.getUriString(); Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() - + " --> " + mPeerProfile + ", interval=" + mInterval); + + " --> " + peerUri + ", interval=" + mInterval); } try { sendKeepAlive(); -- cgit v1.2.3 From 7d2904eaae91bd096eccef637dce430a41923424 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Wed, 29 Jun 2011 18:04:31 +0800 Subject: Make NAT port timeout measurement more flexible. In two ways: (1) When there's a session timeout, restart the measurement at a later time instead of just stalling. (2) When there's a port change, do not re-measure the interval if the current interval works well in the past. We keep success count and decrement it by half when there's a port change. When the count is below a threshold, we restart the measurement process. Change-Id: I7256464435a5e2d2a239bfccaa004e9ceb1d9ce5 --- java/com/android/server/sip/SipService.java | 122 +++++++++++++++++++--------- 1 file changed, 83 insertions(+), 39 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 3b0f546..47863bd 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -756,21 +756,20 @@ public final class SipService extends ISipService.Stub { } } - private class IntervalMeasurementProcess implements + private class IntervalMeasurementProcess implements Runnable, SipSessionGroup.KeepAliveProcessCallback { private static final String TAG = "SipKeepAliveInterval"; private static final int MAX_INTERVAL = 120; // in seconds - private static final int MIN_INTERVAL = 10; // in seconds + private static final int MIN_INTERVAL = 5; // in seconds private static final int PASS_THRESHOLD = 10; private static final int MAX_RETRY_COUNT = 5; + private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; - private boolean mRunning; - private int mMinInterval = 10; // in seconds + private int mMinInterval = DEFAULT_KEEPALIVE_INTERVAL; // in seconds private int mMaxInterval; private int mInterval; private int mPassCount = 0; - private int mErrorCount = 0; public IntervalMeasurementProcess(SipProfile localProfile, int maxInterval) { mMaxInterval = (maxInterval < 0) ? MAX_INTERVAL : maxInterval; @@ -788,8 +787,6 @@ public final class SipService extends ISipService.Stub { // TODO: remove this line once SipWakeupTimer can better handle // variety of timeout values mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); - mSession = (SipSessionGroup.SipSessionImpl) - mGroup.createSession(null); } catch (Exception e) { Log.w(TAG, "start interval measurement error: " + e); } @@ -797,6 +794,11 @@ public final class SipService extends ISipService.Stub { public void start() { synchronized (SipService.this) { + Log.d(TAG, "start measurement w interval=" + mInterval); + if (mSession == null) { + mSession = (SipSessionGroup.SipSessionImpl) + mGroup.createSession(null); + } try { mSession.startKeepAliveProcess(mInterval, this); } catch (SipException e) { @@ -807,14 +809,23 @@ public final class SipService extends ISipService.Stub { public void stop() { synchronized (SipService.this) { - mSession.stopKeepAliveProcess(); + if (mSession != null) { + mSession.stopKeepAliveProcess(); + mSession = null; + } + mTimer.cancel(this); } } private void restart() { synchronized (SipService.this) { + // Return immediately if the measurement process is stopped + if (mSession == null) return; + + Log.d(TAG, "restart measurement w interval=" + mInterval); try { mSession.stopKeepAliveProcess(); + mPassCount = 0; mSession.startKeepAliveProcess(mInterval, this); } catch (SipException e) { Log.e(TAG, "restart()", e); @@ -826,8 +837,6 @@ public final class SipService extends ISipService.Stub { @Override public void onResponse(boolean portChanged) { synchronized (SipService.this) { - mErrorCount = 0; - if (!portChanged) { if (++mPassCount != PASS_THRESHOLD) return; // update the interval, since the current interval is good to @@ -853,7 +862,6 @@ public final class SipService extends ISipService.Stub { } else { // calculate the new interval and continue. mInterval = (mMaxInterval + mMinInterval) / 2; - mPassCount = 0; if (DEBUG) { Log.d(TAG, "current interval: " + mKeepAliveInterval + ", test new interval: " + mInterval); @@ -866,22 +874,32 @@ public final class SipService extends ISipService.Stub { // SipSessionGroup.KeepAliveProcessCallback @Override public void onError(int errorCode, String description) { + Log.w(TAG, "interval measurement error: " + description); + restartLater(); + } + + // timeout handler + @Override + public void run() { + mTimer.cancel(this); + restart(); + } + + private void restartLater() { synchronized (SipService.this) { - Log.w(TAG, "interval measurement error: " + description); - if (++mErrorCount < MAX_RETRY_COUNT) { - Log.w(TAG, " retry count = " + mErrorCount); - mPassCount = 0; - restart(); - } else { - Log.w(TAG, " max retry count reached; measurement aborted"); - } + int interval = NAT_MEASUREMENT_RETRY_INTERVAL; + Log.d(TAG, "Retry measurement " + interval + "s later."); + mTimer.cancel(this); + mTimer.set(interval * 1000, this); } } } private class AutoRegistrationProcess extends SipSessionAdapter implements Runnable, SipSessionGroup.KeepAliveProcessCallback { + private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10; private String TAG = "SipAudoReg"; + private SipSessionGroup.SipSessionImpl mSession; private SipSessionGroup.SipSessionImpl mKeepAliveSession; private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); @@ -892,6 +910,8 @@ public final class SipService extends ISipService.Stub { private String mErrorMessage; private boolean mRunning = false; + private int mKeepAliveSuccessCount = 0; + private String getAction() { return toString(); } @@ -915,18 +935,56 @@ public final class SipService extends ISipService.Stub { } } + private void startKeepAliveProcess(int interval) { + Log.d(TAG, "start keepalive w interval=" + interval); + if (mKeepAliveSession == null) { + mKeepAliveSession = mSession.duplicate(); + } else { + mKeepAliveSession.stopKeepAliveProcess(); + } + try { + mKeepAliveSession.startKeepAliveProcess(interval, this); + } catch (SipException e) { + Log.e(TAG, "failed to start keepalive w interval=" + interval, + e); + } + } + + private void stopKeepAliveProcess() { + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + mKeepAliveSession = null; + } + mKeepAliveSuccessCount = 0; + } + // SipSessionGroup.KeepAliveProcessCallback @Override public void onResponse(boolean portChanged) { synchronized (SipService.this) { if (portChanged) { - restartPortMappingLifetimeMeasurement( - mSession.getLocalProfile(), getKeepAliveInterval()); + int interval = getKeepAliveInterval(); + if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) { + Log.i(TAG, "keepalive doesn't work with interval " + + interval + ", past success count=" + + mKeepAliveSuccessCount); + if (interval > DEFAULT_KEEPALIVE_INTERVAL) { + restartPortMappingLifetimeMeasurement( + mSession.getLocalProfile(), interval); + mKeepAliveSuccessCount = 0; + } + } else { + Log.i(TAG, "keep keepalive going with interval " + + interval + ", past success count=" + + mKeepAliveSuccessCount); + mKeepAliveSuccessCount /= 2; + } } else { // Start keep-alive interval measurement on the first // successfully kept-alive SipSessionGroup startPortMappingLifetimeMeasurement( mSession.getLocalProfile()); + mKeepAliveSuccessCount++; } if (!mRunning || !portChanged) return; @@ -960,10 +1018,7 @@ public final class SipService extends ISipService.Stub { } mTimer.cancel(this); - if (mKeepAliveSession != null) { - mKeepAliveSession.stopKeepAliveProcess(); - mKeepAliveSession = null; - } + stopKeepAliveProcess(); mRegistered = false; setListener(mProxy.getListener()); @@ -975,12 +1030,8 @@ public final class SipService extends ISipService.Stub { if (DEBUGV) { Log.v(TAG, "restart keepalive w interval=" + newInterval); } - mKeepAliveSession.stopKeepAliveProcess(); - try { - mKeepAliveSession.startKeepAliveProcess(newInterval, this); - } catch (SipException e) { - Log.e(TAG, "onKeepAliveIntervalChanged()", e); - } + mKeepAliveSuccessCount = 0; + startKeepAliveProcess(newInterval); } } @@ -1105,14 +1156,7 @@ public final class SipService extends ISipService.Stub { SipProfile localProfile = mSession.getLocalProfile(); if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) || localProfile.getSendKeepAlive())) { - mKeepAliveSession = mSession.duplicate(); - Log.d(TAG, "start keepalive"); - try { - mKeepAliveSession.startKeepAliveProcess( - getKeepAliveInterval(), this); - } catch (SipException e) { - Log.e(TAG, "AutoRegistrationProcess", e); - } + startKeepAliveProcess(getKeepAliveInterval()); } } mMyWakeLock.release(session); -- cgit v1.2.3 From 42cdc7a9d3f94260768f38de4f477ec4e418e19a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Thu, 30 Jun 2011 18:05:39 +0800 Subject: Synchronize SipWakeupTimer.onReceive() to fix the race of two threads that change mPendingIntent; one assigns a new one and the other nullifies it. Change-Id: I5e01f83ea1ac437811d2073839adef9bd0a30ec9 --- java/com/android/server/sip/SipWakeupTimer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java index 76780c0..00d47ac 100644 --- a/java/com/android/server/sip/SipWakeupTimer.java +++ b/java/com/android/server/sip/SipWakeupTimer.java @@ -83,7 +83,7 @@ class SipWakeupTimer extends BroadcastReceiver { mEventQueue = null; } - private synchronized boolean stopped() { + private boolean stopped() { if (mEventQueue == null) { Log.w(TAG, "Timer stopped"); return true; @@ -233,7 +233,7 @@ class SipWakeupTimer extends BroadcastReceiver { } @Override - public void onReceive(Context context, Intent intent) { + public synchronized void onReceive(Context context, Intent intent) { // This callback is already protected by AlarmManager's wake lock. String action = intent.getAction(); if (getAction().equals(action) @@ -261,7 +261,7 @@ class SipWakeupTimer extends BroadcastReceiver { } } - private synchronized void execute(long triggerTime) { + private void execute(long triggerTime) { if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; -- cgit v1.2.3 From 987b505b1c190ff0e5a05d2cc20c9b08d2b99b18 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 1 Jul 2011 19:25:46 +0800 Subject: Do not hold wifi lock when SIP is also available over mobile network. Bug: 3111564 Change-Id: Ifc76e5c378d620e40ce4adf6ffa20807e9750fdb --- java/com/android/server/sip/SipService.java | 51 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 47863bd..ddc8031 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -83,6 +83,8 @@ public final class SipService extends ISipService.Stub { private WifiScanProcess mWifiScanProcess; private WifiManager.WifiLock mWifiLock; private boolean mWifiOnly; + private BroadcastReceiver mWifiStateReceiver = null; + private IntervalMeasurementProcess mIntervalMeasurementProcess; private MyExecutor mExecutor = new MyExecutor(); @@ -123,40 +125,47 @@ public final class SipService extends ISipService.Stub { mWifiOnly = SipManager.isSipWifiOnly(context); } - private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { - int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN); - synchronized (SipService.this) { - switch (state) { - case WifiManager.WIFI_STATE_ENABLED: - mWifiEnabled = true; - if (anyOpenedToReceiveCalls()) grabWifiLock(); - break; - case WifiManager.WIFI_STATE_DISABLED: - mWifiEnabled = false; - releaseWifiLock(); - break; + private BroadcastReceiver createWifiBroadcastReceiver() { + return new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN); + synchronized (SipService.this) { + switch (state) { + case WifiManager.WIFI_STATE_ENABLED: + mWifiEnabled = true; + if (anyOpenedToReceiveCalls()) grabWifiLock(); + break; + case WifiManager.WIFI_STATE_DISABLED: + mWifiEnabled = false; + releaseWifiLock(); + break; + } } } } - } + }; }; private void registerReceivers() { mContext.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - mContext.registerReceiver(mWifiStateReceiver, - new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + if (SipManager.isSipWifiOnly(mContext)) { + mWifiStateReceiver = createWifiBroadcastReceiver(); + mContext.registerReceiver(mWifiStateReceiver, + new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); + } if (DEBUG) Log.d(TAG, " +++ register receivers"); } private void unregisterReceivers() { mContext.unregisterReceiver(mConnectivityReceiver); - mContext.unregisterReceiver(mWifiStateReceiver); + if (SipManager.isSipWifiOnly(mContext)) { + mContext.unregisterReceiver(mWifiStateReceiver); + } if (DEBUG) Log.d(TAG, " --- unregister receivers"); } -- cgit v1.2.3 From 108e51ec4c06c75db2a11d1417f1bc8c0545ea8b Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 1 Jul 2011 19:43:00 +0800 Subject: Do not keep alive for re-established call. Only need to keep alive for caller in a newly established call. Change-Id: I36f9d9499c806c8701e3b78555de399b00593be8 --- java/com/android/server/sip/SipSessionGroup.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 047eb8d..4e44402 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -1223,9 +1223,9 @@ class SipSessionGroup implements SipListener { private void establishCall(boolean enableKeepAlive) { mState = SipSession.State.IN_CALL; - mInCall = true; cancelSessionTimer(); - if (enableKeepAlive) enableKeepAlive(); + if (!mInCall && enableKeepAlive) enableKeepAlive(); + mInCall = true; mProxy.onCallEstablished(this, mPeerSessionDescription); } -- cgit v1.2.3 From cd9cb0845a0ad468bbbadadc133bc7a406e40b4a Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Fri, 1 Jul 2011 20:32:35 +0800 Subject: Keep last known keepalive interval to avoid duplicate effort. The current implementation always starts with default minimum interval when the measurement process starts. By keeping last known good interval, we can save the time in re-measurement. Change-Id: I8f1720acafaa7e101855fe0c66d5c7b0e578e0d7 --- java/com/android/server/sip/SipService.java | 51 ++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index ddc8031..c553947 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -74,6 +74,7 @@ public final class SipService extends ISipService.Stub { private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds + private static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds private Context mContext; private String mLocalIp; @@ -101,6 +102,7 @@ public final class SipService extends ISipService.Stub { private boolean mWifiEnabled; private SipWakeLock mMyWakeLock; private int mKeepAliveInterval; + private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; /** * Starts the SIP service. Do nothing if the SIP API is not supported on the @@ -448,6 +450,7 @@ public final class SipService extends ISipService.Stub { if (connected) { mLocalIp = determineLocalIp(); mKeepAliveInterval = -1; + mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } @@ -471,7 +474,8 @@ public final class SipService extends ISipService.Stub { private void startPortMappingLifetimeMeasurement( SipProfile localProfile) { - startPortMappingLifetimeMeasurement(localProfile, -1); + startPortMappingLifetimeMeasurement(localProfile, + DEFAULT_MAX_KEEPALIVE_INTERVAL); } private void startPortMappingLifetimeMeasurement( @@ -482,8 +486,16 @@ public final class SipService extends ISipService.Stub { Log.d(TAG, "start NAT port mapping timeout measurement on " + localProfile.getUriString()); - mIntervalMeasurementProcess = - new IntervalMeasurementProcess(localProfile, maxInterval); + int minInterval = mLastGoodKeepAliveInterval; + if (minInterval >= maxInterval) { + // If mLastGoodKeepAliveInterval also does not work, reset it + // to the default min + minInterval = mLastGoodKeepAliveInterval + = DEFAULT_KEEPALIVE_INTERVAL; + Log.d(TAG, " reset min interval to " + minInterval); + } + mIntervalMeasurementProcess = new IntervalMeasurementProcess( + localProfile, minInterval, maxInterval); mIntervalMeasurementProcess.start(); } } @@ -539,7 +551,7 @@ public final class SipService extends ISipService.Stub { private int getKeepAliveInterval() { return (mKeepAliveInterval < 0) - ? DEFAULT_KEEPALIVE_INTERVAL + ? mLastGoodKeepAliveInterval : mKeepAliveInterval; } @@ -768,27 +780,33 @@ public final class SipService extends ISipService.Stub { private class IntervalMeasurementProcess implements Runnable, SipSessionGroup.KeepAliveProcessCallback { private static final String TAG = "SipKeepAliveInterval"; - private static final int MAX_INTERVAL = 120; // in seconds private static final int MIN_INTERVAL = 5; // in seconds private static final int PASS_THRESHOLD = 10; private static final int MAX_RETRY_COUNT = 5; private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; - private int mMinInterval = DEFAULT_KEEPALIVE_INTERVAL; // in seconds + private int mMinInterval; private int mMaxInterval; private int mInterval; private int mPassCount = 0; - public IntervalMeasurementProcess(SipProfile localProfile, int maxInterval) { - mMaxInterval = (maxInterval < 0) ? MAX_INTERVAL : maxInterval; - mInterval = (mMaxInterval + mMinInterval) / 2; + public IntervalMeasurementProcess(SipProfile localProfile, + int minInterval, int maxInterval) { + mMaxInterval = maxInterval; + mMinInterval = minInterval; + mInterval = (maxInterval + minInterval) / 2; // Don't start measurement if the interval is too small - if (mInterval < MIN_INTERVAL) { + if (mInterval < DEFAULT_KEEPALIVE_INTERVAL) { Log.w(TAG, "interval is too small; measurement aborted; " + "maxInterval=" + mMaxInterval); return; + } else if (checkTermination()) { + Log.w(TAG, "interval is too small; measurement aborted; " + + "interval=[" + mMinInterval + "," + mMaxInterval + + "]"); + return; } try { @@ -842,6 +860,10 @@ public final class SipService extends ISipService.Stub { } } + private boolean checkTermination() { + return ((mMaxInterval - mMinInterval) < MIN_INTERVAL); + } + // SipSessionGroup.KeepAliveProcessCallback @Override public void onResponse(boolean portChanged) { @@ -850,6 +872,9 @@ public final class SipService extends ISipService.Stub { if (++mPassCount != PASS_THRESHOLD) return; // update the interval, since the current interval is good to // keep the port mapping. + if (mKeepAliveInterval > 0) { + mLastGoodKeepAliveInterval = mKeepAliveInterval; + } mKeepAliveInterval = mMinInterval = mInterval; if (DEBUG) { Log.d(TAG, "measured good keepalive interval: " @@ -860,9 +885,13 @@ public final class SipService extends ISipService.Stub { // Since the rport is changed, shorten the interval. mMaxInterval = mInterval; } - if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { + if (checkTermination()) { // update mKeepAliveInterval and stop measurement. stop(); + // If all the measurements failed, we still set it to + // mMinInterval; If mMinInterval still doesn't work, a new + // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL + // will be conducted. mKeepAliveInterval = mMinInterval; if (DEBUG) { Log.d(TAG, "measured keepalive interval: " -- cgit v1.2.3 From f0af349a5b7b7df47e6b1b53e028cecdff50caa6 Mon Sep 17 00:00:00 2001 From: repo sync Date: Tue, 12 Jul 2011 08:30:20 +0800 Subject: Add REFER handling. Handle REFER requests including REFER with Replaces header. bug:4958680 Change-Id: I96df95097b78bed67ab8abd309a1e57a45c6bc2f --- java/android/net/sip/SipAudioCall.java | 11 ++- java/com/android/server/sip/SipHelper.java | 32 +++++- java/com/android/server/sip/SipSessionGroup.java | 120 ++++++++++++++++++----- 3 files changed, 133 insertions(+), 30 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index c1affa6..fcdbd2c 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -57,6 +57,7 @@ public class SipAudioCall { 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 static final int TRANSFER_TIMEOUT = 15; // in seconds /** Listener for events relating to a SIP call, such as when a call is being * recieved ("on ringing") or a call is outgoing ("on calling"). @@ -537,10 +538,14 @@ public class SipAudioCall { Log.v(TAG, "onCallTransferring mSipSession:" + mSipSession + " newSession:" + newSession); mTransferringSession = newSession; - // session changing request try { - String answer = createAnswer(sessionDescription).encode(); - newSession.answerCall(answer, SESSION_TIMEOUT); + if (sessionDescription == null) { + newSession.makeCall(newSession.getPeerProfile(), + createOffer().encode(), TRANSFER_TIMEOUT); + } else { + String answer = createAnswer(sessionDescription).encode(); + newSession.answerCall(answer, SESSION_TIMEOUT); + } } catch (Throwable e) { Log.e(TAG, "onCallTransferring()", e); newSession.endCall(); diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index c031bc1..dc628e0 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -19,6 +19,9 @@ package com.android.server.sip; import gov.nist.javax.sip.SipStackExt; import gov.nist.javax.sip.clientauthutils.AccountManager; import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; +import gov.nist.javax.sip.header.extensions.ReferencesHeader; +import gov.nist.javax.sip.header.extensions.ReferredByHeader; +import gov.nist.javax.sip.header.extensions.ReplacesHeader; import android.net.sip.SipProfile; import android.util.Log; @@ -284,14 +287,18 @@ class SipHelper { } public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, - String sessionDescription, String tag) - throws SipException { + String sessionDescription, String tag, ReferredByHeader referredBy, + String replaces) throws SipException { try { Request request = createRequest(Request.INVITE, caller, callee, tag); + if (referredBy != null) request.addHeader(referredBy); + if (replaces != null) { + request.addHeader(mHeaderFactory.createHeader( + ReplacesHeader.NAME, replaces)); + } request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); - ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); if (DEBUG) Log.d(TAG, "send INVITE: " + request); @@ -455,6 +462,25 @@ class SipHelper { } } + public void sendReferNotify(Dialog dialog, String content) + throws SipException { + try { + Request request = dialog.createRequest(Request.NOTIFY); + request.addHeader(mHeaderFactory.createSubscriptionStateHeader( + "active;expires=60")); + // set content here + request.setContent(content, + mHeaderFactory.createContentTypeHeader( + "message", "sipfrag")); + request.addHeader(mHeaderFactory.createEventHeader( + ReferencesHeader.REFER)); + if (DEBUG) Log.d(TAG, "send NOTIFY: " + request); + dialog.sendRequest(mSipProvider.getNewClientTransaction(request)); + } catch (ParseException e) { + throw new SipException("sendReferNotify()", e); + } + } + public void sendInviteRequestTerminated(Request inviteRequest, ServerTransaction inviteTransaction) throws SipException { try { diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 4e44402..48d9b17 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -18,12 +18,15 @@ package com.android.server.sip; import gov.nist.javax.sip.clientauthutils.AccountManager; import gov.nist.javax.sip.clientauthutils.UserCredentials; -import gov.nist.javax.sip.header.SIPHeaderNames; import gov.nist.javax.sip.header.ProxyAuthenticate; +import gov.nist.javax.sip.header.ReferTo; +import gov.nist.javax.sip.header.SIPHeaderNames; +import gov.nist.javax.sip.header.StatusLine; import gov.nist.javax.sip.header.WWWAuthenticate; import gov.nist.javax.sip.header.extensions.ReferredByHeader; import gov.nist.javax.sip.header.extensions.ReplacesHeader; import gov.nist.javax.sip.message.SIPMessage; +import gov.nist.javax.sip.message.SIPResponse; import android.net.sip.ISipSession; import android.net.sip.ISipSessionListener; @@ -71,12 +74,15 @@ import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.FromHeader; +import javax.sip.header.HeaderAddress; import javax.sip.header.MinExpiresHeader; +import javax.sip.header.ReferToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Message; import javax.sip.message.Request; import javax.sip.message.Response; + /** * Manages {@link ISipSession}'s for a SIP account. */ @@ -390,25 +396,26 @@ class SipSessionGroup implements SipListener { } } + private SipSessionImpl createNewSession(RequestEvent event, + ISipSessionListener listener, ServerTransaction transaction, + int newState) throws SipException { + SipSessionImpl newSession = new SipSessionImpl(listener); + newSession.mServerTransaction = transaction; + newSession.mState = newState; + newSession.mDialog = newSession.mServerTransaction.getDialog(); + newSession.mInviteReceived = event; + newSession.mPeerProfile = createPeerProfile((HeaderAddress) + event.getRequest().getHeader(FromHeader.NAME)); + newSession.mPeerSessionDescription = + extractContent(event.getRequest()); + return newSession; + } + private class SipSessionCallReceiverImpl extends SipSessionImpl { public SipSessionCallReceiverImpl(ISipSessionListener listener) { super(listener); } - private SipSessionImpl createNewSession(RequestEvent event, - ISipSessionListener listener, ServerTransaction transaction) - throws SipException { - SipSessionImpl newSession = new SipSessionImpl(listener); - newSession.mServerTransaction = transaction; - newSession.mState = SipSession.State.INCOMING_CALL; - newSession.mDialog = newSession.mServerTransaction.getDialog(); - newSession.mInviteReceived = event; - newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mPeerSessionDescription = - extractContent(event.getRequest()); - return newSession; - } - private int processInviteWithReplaces(RequestEvent event, ReplacesHeader replaces) { String callId = replaces.getCallId(); @@ -452,7 +459,8 @@ class SipSessionGroup implements SipListener { // got INVITE w/ replaces request. newSession = createNewSession(event, replacedSession.mProxy.getListener(), - mSipHelper.getServerTransaction(event)); + mSipHelper.getServerTransaction(event), + SipSession.State.INCOMING_CALL); newSession.mProxy.onCallTransferring(newSession, newSession.mPeerSessionDescription); } else { @@ -461,7 +469,8 @@ class SipSessionGroup implements SipListener { } else { // New Incoming call. newSession = createNewSession(event, mProxy, - mSipHelper.sendRinging(event, generateTag())); + mSipHelper.sendRinging(event, generateTag()), + SipSession.State.INCOMING_CALL); mProxy.onRinging(newSession, newSession.mPeerProfile, newSession.mPeerSessionDescription); } @@ -507,6 +516,11 @@ class SipSessionGroup implements SipListener { private SipSessionImpl mKeepAliveSession; + // the following three members are used for handling refer request. + SipSessionImpl mReferSession; + ReferredByHeader mReferredBy; + String mReplaces; + // lightweight timer class SessionTimer { private boolean mRunning = true; @@ -556,6 +570,9 @@ class SipSessionGroup implements SipListener { mInviteReceived = null; mPeerSessionDescription = null; mAuthenticationRetryCount = 0; + mReferSession = null; + mReferredBy = null; + mReplaces = null; if (mDialog != null) mDialog.delete(); mDialog = null; @@ -969,15 +986,26 @@ class SipSessionGroup implements SipListener { return (proxyAuth == null) ? null : proxyAuth.getNonce(); } + private String getResponseString(int statusCode) { + StatusLine statusLine = new StatusLine(); + statusLine.setStatusCode(statusCode); + statusLine.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode)); + return statusLine.encode(); + } + private boolean readyForCall(EventObject evt) throws SipException { // expect MakeCallCommand, RegisterCommand, DEREGISTER if (evt instanceof MakeCallCommand) { mState = SipSession.State.OUTGOING_CALL; MakeCallCommand cmd = (MakeCallCommand) evt; mPeerProfile = cmd.getPeerProfile(); - mClientTransaction = mSipHelper.sendInvite(mLocalProfile, - mPeerProfile, cmd.getSessionDescription(), - generateTag()); + if (mReferSession != null) { + mSipHelper.sendReferNotify(mReferSession.mDialog, + getResponseString(Response.TRYING)); + } + mClientTransaction = mSipHelper.sendInvite( + mLocalProfile, mPeerProfile, cmd.getSessionDescription(), + generateTag(), mReferredBy, mReplaces); mDialog = mClientTransaction.getDialog(); addSipSession(this); startSessionTimer(cmd.getTimeout()); @@ -1072,6 +1100,12 @@ class SipSessionGroup implements SipListener { } return true; case Response.OK: + if (mReferSession != null) { + mSipHelper.sendReferNotify(mReferSession.mDialog, + getResponseString(Response.OK)); + // since we don't need to remember the session anymore. + mReferSession = null; + } mSipHelper.sendInviteAck(event, mDialog); mPeerSessionDescription = extractContent(response); establishCall(true); @@ -1087,6 +1121,10 @@ class SipSessionGroup implements SipListener { // rfc3261#section-14.1; re-schedule invite return true; default: + if (mReferSession != null) { + mSipHelper.sendReferNotify(mReferSession.mDialog, + getResponseString(Response.SERVICE_UNAVAILABLE)); + } if (statusCode >= 400) { // error: an ack is sent automatically by the stack onError(response); @@ -1155,6 +1193,38 @@ class SipSessionGroup implements SipListener { return false; } + private boolean processReferRequest(RequestEvent event) + throws SipException { + try { + ReferToHeader referto = (ReferToHeader) event.getRequest() + .getHeader(ReferTo.NAME); + Address address = referto.getAddress(); + SipURI uri = (SipURI) address.getURI(); + String replacesHeader = uri.getHeader(ReplacesHeader.NAME); + String username = uri.getUser(); + if (username == null) { + mSipHelper.sendResponse(event, Response.BAD_REQUEST); + return false; + } + // send notify accepted + mSipHelper.sendResponse(event, Response.ACCEPTED); + SipSessionImpl newSession = createNewSession(event, + this.mProxy.getListener(), + mSipHelper.getServerTransaction(event), + SipSession.State.READY_TO_CALL); + newSession.mReferSession = this; + newSession.mReferredBy = (ReferredByHeader) event.getRequest() + .getHeader(ReferredByHeader.NAME); + newSession.mReplaces = replacesHeader; + newSession.mPeerProfile = createPeerProfile(referto); + newSession.mProxy.onCallTransferring(newSession, + null); + return true; + } catch (IllegalArgumentException e) { + throw new SipException("createPeerProfile()", e); + } + } + private boolean inCall(EventObject evt) throws SipException { // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) // OK retransmission is handled in SipStack @@ -1175,6 +1245,8 @@ class SipSessionGroup implements SipListener { mSipHelper.sendResponse((RequestEvent) evt, Response.OK); endCallNormally(); return true; + } else if (isRequestEvent(Request.REFER, evt)) { + return processReferRequest((RequestEvent) evt); } else if (evt instanceof MakeCallCommand) { // to change call mState = SipSession.State.OUTGOING_CALL; @@ -1182,6 +1254,8 @@ class SipSessionGroup implements SipListener { ((MakeCallCommand) evt).getSessionDescription()); startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; + } else if (evt instanceof ResponseEvent) { + if (expectResponse(Request.NOTIFY, evt)) return true; } return false; } @@ -1558,12 +1632,10 @@ class SipSessionGroup implements SipListener { return false; } - private static SipProfile createPeerProfile(Request request) + private static SipProfile createPeerProfile(HeaderAddress header) throws SipException { try { - FromHeader fromHeader = - (FromHeader) request.getHeader(FromHeader.NAME); - Address address = fromHeader.getAddress(); + Address address = header.getAddress(); SipURI uri = (SipURI) address.getURI(); String username = uri.getUser(); if (username == null) username = ANONYMOUS; -- cgit v1.2.3 From ff0c3ac9f0e9aa22c0d4cc2b1ca051e1974287ea Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Mon, 25 Jul 2011 12:02:16 -0700 Subject: Issue 3370834: No Echo canceler for SIP Added detection of platfrom AEC in AudioGroup. If an AEC is present, the SIP stack will use it, otherwise the echo suppressor of the stack will be used. Change-Id: I4aa45a8868466120f5f9fae71b491fe4ae1162c2 --- jni/rtp/Android.mk | 3 +- jni/rtp/AudioGroup.cpp | 81 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 47ed658..0815294 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -49,7 +49,8 @@ LOCAL_C_INCLUDES += \ frameworks/base/media/libstagefright/codecs/amrnb/enc/include \ frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ frameworks/base/media/libstagefright/codecs/amrnb/dec/include \ - frameworks/base/media/libstagefright/codecs/amrnb/dec/src + frameworks/base/media/libstagefright/codecs/amrnb/dec/src \ + system/media/audio_effects/include LOCAL_CFLAGS += -fvisibility=hidden diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 85ed1c8..529b425 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -40,7 +40,8 @@ #include #include #include - +#include +#include #include #include "jni.h" @@ -481,6 +482,7 @@ public: bool sendDtmf(int event); bool add(AudioStream *stream); bool remove(int socket); + bool platformHasAec() { return mPlatformHasAec; } private: enum { @@ -491,6 +493,8 @@ private: LAST_MODE = 3, }; + bool checkPlatformAec(); + AudioStream *mChain; int mEventQueue; volatile int mDtmfEvent; @@ -499,6 +503,7 @@ private: int mSampleRate; int mSampleCount; int mDeviceSocket; + bool mPlatformHasAec; class NetworkThread : public Thread { @@ -550,6 +555,7 @@ AudioGroup::AudioGroup() mDeviceSocket = -1; mNetworkThread = new NetworkThread(this); mDeviceThread = new DeviceThread(this); + mPlatformHasAec = checkPlatformAec(); } AudioGroup::~AudioGroup() @@ -630,10 +636,6 @@ bool AudioGroup::setMode(int mode) if (mode == NORMAL && !strcmp(value, "herring")) { mode = ECHO_SUPPRESSION; } - if (mode == ECHO_SUPPRESSION && AudioSystem::getParameters( - 0, String8("ec_supported")) == "ec_supported=yes") { - mode = NORMAL; - } if (mMode == mode) { return true; } @@ -759,6 +761,25 @@ bool AudioGroup::NetworkThread::threadLoop() return true; } +bool AudioGroup::checkPlatformAec() +{ + effect_descriptor_t fxDesc; + uint32_t numFx; + + if (AudioEffect::queryNumberEffects(&numFx) != NO_ERROR) { + return false; + } + for (uint32_t i = 0; i < numFx; i++) { + if (AudioEffect::queryEffect(i, &fxDesc) != NO_ERROR) { + continue; + } + if (memcmp(&fxDesc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) { + return true; + } + } + return false; +} + bool AudioGroup::DeviceThread::threadLoop() { int mode = mGroup->mMode; @@ -798,10 +819,6 @@ bool AudioGroup::DeviceThread::threadLoop() } LOGD("latency: output %d, input %d", track.latency(), record.latency()); - // Initialize echo canceler. - EchoSuppressor echo(sampleCount, - (track.latency() + record.latency()) * sampleRate / 1000); - // Give device socket a reasonable buffer size. setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output)); @@ -810,6 +827,33 @@ bool AudioGroup::DeviceThread::threadLoop() char c; while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); + // check if platform supports echo cancellation and do not active local echo suppression in + // this case + EchoSuppressor *echo = NULL; + AudioEffect *aec = NULL; + if (mode == ECHO_SUPPRESSION) { + if (mGroup->platformHasAec()) { + aec = new AudioEffect(FX_IID_AEC, + NULL, + 0, + 0, + 0, + record.getSessionId(), + record.getInput()); + status_t status = aec->initCheck(); + if (status == NO_ERROR || status == ALREADY_EXISTS) { + aec->setEnabled(true); + } else { + delete aec; + aec = NULL; + } + } + // Create local echo suppressor if platform AEC cannot be used. + if (aec == NULL) { + echo = new EchoSuppressor(sampleCount, + (track.latency() + record.latency()) * sampleRate / 1000); + } + } // Start AudioRecord before AudioTrack. This prevents AudioTrack from being // disabled due to buffer underrun while waiting for AudioRecord. if (mode != MUTED) { @@ -843,7 +887,7 @@ bool AudioGroup::DeviceThread::threadLoop() track.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { LOGE("cannot write to AudioTrack"); - return true; + goto exit; } } @@ -859,7 +903,7 @@ bool AudioGroup::DeviceThread::threadLoop() record.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { LOGE("cannot read from AudioRecord"); - return true; + goto exit; } } } @@ -870,15 +914,18 @@ bool AudioGroup::DeviceThread::threadLoop() } if (mode != MUTED) { - if (mode == NORMAL) { - send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); - } else { - echo.run(output, input); - send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); + if (echo != NULL) { + LOGV("echo->run()"); + echo->run(output, input); } + send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); } } - return false; + +exit: + delete echo; + delete aec; + return true; } //------------------------------------------------------------------------------ -- cgit v1.2.3 From 088ef46d8b5a849d80a257670bb2a809713b7bf0 Mon Sep 17 00:00:00 2001 From: Masahiko Endo Date: Thu, 28 Jul 2011 21:51:43 +0900 Subject: Prevent NullPointerException cases while using SipService. Some SipService methods may return null, in such cases like no Wi-Fi connection. Added minimum check to prevent NullPointerExceptions. Change-Id: Ia7fae57ee893f2564cbfdedb6dc614938ab60ff7 Signed-off-by: Masahiko Endo --- java/android/net/sip/SipManager.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index dce46fe..cd0b5c4 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -471,6 +471,10 @@ public class SipManager { try { ISipSession session = mSipService.createSession(localProfile, createRelay(listener, localProfile.getUriString())); + if (session == null) { + throw new SipException( + "SipService.createSession() returns null"); + } session.register(expiryTime); } catch (RemoteException e) { throw new SipException("register()", e); @@ -492,6 +496,10 @@ public class SipManager { try { ISipSession session = mSipService.createSession(localProfile, createRelay(listener, localProfile.getUriString())); + if (session == null) { + throw new SipException( + "SipService.createSession() returns null"); + } session.unregister(); } catch (RemoteException e) { throw new SipException("unregister()", e); @@ -513,7 +521,7 @@ public class SipManager { try { String callId = getCallId(incomingCallIntent); ISipSession s = mSipService.getPendingSession(callId); - return new SipSession(s); + return ((s == null) ? null : new SipSession(s)); } catch (RemoteException e) { throw new SipException("getSessionFor()", e); } -- cgit v1.2.3 From ceb525d85512bde884b0f01a3aa93f2943dcf5f3 Mon Sep 17 00:00:00 2001 From: Hung-ying Tyan Date: Mon, 15 Aug 2011 01:07:34 +0800 Subject: Handle SIP authentication response for BYE. Bug: 5159669 Change-Id: I029684334500d4d0db176783084c9b7d1db87e40 --- java/android/net/sip/SipSession.java | 3 +++ java/com/android/server/sip/SipSessionGroup.java | 30 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java index 5ba1626..e03cf9f 100644 --- a/java/android/net/sip/SipSession.java +++ b/java/android/net/sip/SipSession.java @@ -63,6 +63,9 @@ public final class SipSession { /** When an OPTIONS request is sent. */ public static final int PINGING = 9; + /** When ending a call. @hide */ + public static final int ENDING_CALL = 10; + /** Not defined. */ public static final int NOT_DEFINED = 101; diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 48d9b17..3b3cbf3 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -98,6 +98,7 @@ class SipSessionGroup implements SipListener { private static final String THREAD_POOL_SIZE = "1"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds + private static final int END_CALL_TIMER = 3; // in seconds private static final int KEEPALIVE_TIMEOUT = 3; // in seconds private static final int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds @@ -756,6 +757,9 @@ class SipSessionGroup implements SipListener { case SipSession.State.IN_CALL: processed = inCall(evt); break; + case SipSession.State.ENDING_CALL: + processed = endingCall(evt); + break; default: processed = false; } @@ -1230,8 +1234,10 @@ class SipSessionGroup implements SipListener { // OK retransmission is handled in SipStack if (END_CALL == evt) { // rfc3261#section-15.1.1 + mState = SipSession.State.ENDING_CALL; mSipHelper.sendBye(mDialog); - endCallNormally(); + mProxy.onCallEnded(this); + startSessionTimer(END_CALL_TIMER); return true; } else if (isRequestEvent(Request.INVITE, evt)) { // got Re-INVITE @@ -1260,6 +1266,28 @@ class SipSessionGroup implements SipListener { return false; } + private boolean endingCall(EventObject evt) throws SipException { + if (expectResponse(Request.BYE, evt)) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + + int statusCode = response.getStatusCode(); + switch (statusCode) { + case Response.UNAUTHORIZED: + case Response.PROXY_AUTHENTICATION_REQUIRED: + if (handleAuthentication(event)) { + return true; + } else { + // can't authenticate; pass through to end session + } + } + cancelSessionTimer(); + reset(); + return true; + } + return false; + } + // timeout in seconds private void startSessionTimer(int timeout) { if (timeout > 0) { -- cgit v1.2.3 From 5e75bad33a53a468440871e4dbf83d05e6642de4 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 30 Aug 2011 13:58:35 -0700 Subject: SIP: add the check for expiry time in Contact header. There can be three expiry times in the same message header. We choose the smaller value in Expires header and Contact header, and then we obey the value defined in Min-Expires header. If none of them is set, the default value is used. Bug: 5178284 Change-Id: Ie9d4a48c93863e82e5197bb4a0db3f4fec56857c --- java/com/android/server/sip/SipSessionGroup.java | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 3b3cbf3..49effa8 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -72,6 +72,7 @@ import javax.sip.TransactionUnavailableException; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; +import javax.sip.header.ContactHeader; import javax.sip.header.ExpiresHeader; import javax.sip.header.FromHeader; import javax.sip.header.HeaderAddress; @@ -873,16 +874,21 @@ class SipSessionGroup implements SipListener { } private int getExpiryTime(Response response) { - int expires = EXPIRY_TIME; - ExpiresHeader expiresHeader = (ExpiresHeader) - response.getHeader(ExpiresHeader.NAME); - if (expiresHeader != null) expires = expiresHeader.getExpires(); - expiresHeader = (ExpiresHeader) - response.getHeader(MinExpiresHeader.NAME); - if (expiresHeader != null) { - expires = Math.max(expires, expiresHeader.getExpires()); - } - return expires; + int time = -1; + ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME); + if (contact != null) { + time = contact.getExpires(); + } + ExpiresHeader expires = (ExpiresHeader) response.getHeader(ExpiresHeader.NAME); + if (expires != null && (time < 0 || time > expires.getExpires())) { + time = expires.getExpires(); + } + expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME); + if (expires != null && time < expires.getExpires()) { + time = expires.getExpires(); + } + Log.v(TAG, "Expiry time = " + time); + return (time > 0) ? time : EXPIRY_TIME; } private boolean registeringToReady(EventObject evt) -- cgit v1.2.3 From 6089a999b71162cd94ed2322670d7a2090b7d094 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Mon, 29 Aug 2011 14:24:31 -0700 Subject: VoIP JNI: Force AEC on for tuna board Force AEC on for tuna board because of the strong feedback of Rx audio path, even when playing over earpiece or headset. Change-Id: I9c14257d56103ba82d6cdb0b7d5a3f315638136e --- jni/rtp/AudioGroup.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 529b425..5f07bb5 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -628,12 +628,13 @@ bool AudioGroup::setMode(int mode) if (mode < 0 || mode > LAST_MODE) { return false; } - //FIXME: temporary code to overcome echo and mic gain issues on herring board. - // Must be modified/removed when proper support for voice processing query and control - // is included in audio framework + // FIXME: temporary code to overcome echo and mic gain issues on herring and tuna boards. + // Must be modified/removed when the root cause of the issue is fixed in the hardware or + // driver char value[PROPERTY_VALUE_MAX]; property_get("ro.product.board", value, ""); - if (mode == NORMAL && !strcmp(value, "herring")) { + if (mode == NORMAL && + (!strcmp(value, "herring") || !strcmp(value, "tuna"))) { mode = ECHO_SUPPRESSION; } if (mMode == mode) { -- cgit v1.2.3 From a3d53b09556994c514640e69d84f40c655347692 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 6 Sep 2011 12:47:12 -0700 Subject: SIP: avoid extreme small values in Min-Expires headers. If the expiry time cannot be found in Contact header or Expires header, use the default value of 3600 seconds, which is specified in RFC 3261. Change-Id: I2607a398b96743614b01713cfd9b28f40386fac1 --- java/com/android/server/sip/SipSessionGroup.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 49effa8..eb5cce7 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -883,12 +883,15 @@ class SipSessionGroup implements SipListener { if (expires != null && (time < 0 || time > expires.getExpires())) { time = expires.getExpires(); } + if (time <= 0) { + time = EXPIRY_TIME; + } expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME); if (expires != null && time < expires.getExpires()) { time = expires.getExpires(); } Log.v(TAG, "Expiry time = " + time); - return (time > 0) ? time : EXPIRY_TIME; + return time; } private boolean registeringToReady(EventObject evt) -- cgit v1.2.3 From 418b5f04c2eb3ceff046328ba23a16b89a5a3306 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 6 Sep 2011 14:18:37 -0700 Subject: RTP: support payloads with larger packetization interval. RFC 3551 section 4.2 said that a receiver should accept packets representing between 0 and 200ms of audio data. Now we add the ability to decode multiple frames in a payload as long as the jitter buffer is not full. This change covers G711, GSM, and GSM-EFR. AMR will be added later. Bug: 3029736 Change-Id: Ifd194596766d14f02177925c58432cd620e44dd7 --- jni/rtp/AmrCodec.cpp | 20 ++++++++++++-------- jni/rtp/AudioCodec.h | 2 +- jni/rtp/AudioGroup.cpp | 21 +++++++++++---------- jni/rtp/G711Codec.cpp | 14 ++++++++++---- jni/rtp/GsmCodec.cpp | 16 ++++++++++------ 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/jni/rtp/AmrCodec.cpp b/jni/rtp/AmrCodec.cpp index 84c7166..e2d820e 100644 --- a/jni/rtp/AmrCodec.cpp +++ b/jni/rtp/AmrCodec.cpp @@ -52,7 +52,7 @@ public: int set(int sampleRate, const char *fmtp); int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); + int decode(int16_t *samples, int count, void *payload, int length); private: void *mEncoder; @@ -128,7 +128,7 @@ int AmrCodec::encode(void *payload, int16_t *samples) return length; } -int AmrCodec::decode(int16_t *samples, void *payload, int length) +int AmrCodec::decode(int16_t *samples, int count, void *payload, int length) { unsigned char *bytes = (unsigned char *)payload; Frame_Type_3GPP type; @@ -213,7 +213,7 @@ public: } int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); + int decode(int16_t *samples, int count, void *payload, int length); private: void *mEncoder; @@ -239,20 +239,24 @@ int GsmEfrCodec::encode(void *payload, int16_t *samples) return -1; } -int GsmEfrCodec::decode(int16_t *samples, void *payload, int length) +int GsmEfrCodec::decode(int16_t *samples, int count, void *payload, int length) { unsigned char *bytes = (unsigned char *)payload; - if (length == 31 && (bytes[0] >> 4) == 0x0C) { + int n = 0; + while (n + 160 <= count && length >= 31 && (bytes[0] >> 4) == 0x0C) { for (int i = 0; i < 30; ++i) { bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4); } bytes[30] <<= 4; - if (AMRDecode(mDecoder, AMR_122, bytes, samples, MIME_IETF) == 31) { - return 160; + if (AMRDecode(mDecoder, AMR_122, bytes, &samples[n], MIME_IETF) != 31) { + break; } + n += 160; + length -= 31; + bytes += 31; } - return -1; + return n; } } // namespace diff --git a/jni/rtp/AudioCodec.h b/jni/rtp/AudioCodec.h index e389255..741730b 100644 --- a/jni/rtp/AudioCodec.h +++ b/jni/rtp/AudioCodec.h @@ -30,7 +30,7 @@ public: // Returns the length of payload in bytes. virtual int encode(void *payload, int16_t *samples) = 0; // Returns the number of decoded samples. - virtual int decode(int16_t *samples, void *payload, int length) = 0; + virtual int decode(int16_t *samples, int count, void *payload, int length) = 0; }; AudioCodec *newAudioCodec(const char *codecName); diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 529b425..9b0455c 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -395,7 +395,8 @@ void AudioStream::decode(int tick) mLatencyTimer = tick; } - if (mBufferTail - mBufferHead > BUFFER_SIZE - mInterval) { + int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; + if (count < mSampleCount) { // Buffer overflow. Drop the packet. LOGV("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); @@ -403,19 +404,18 @@ void AudioStream::decode(int tick) } // Receive the packet and decode it. - int16_t samples[mSampleCount]; - int length = 0; + int16_t samples[count]; if (!mCodec) { // Special case for device stream. - length = recv(mSocket, samples, sizeof(samples), + count = recv(mSocket, samples, sizeof(samples), MSG_TRUNC | MSG_DONTWAIT) >> 1; } else { __attribute__((aligned(4))) uint8_t buffer[2048]; sockaddr_storage remote; - socklen_t len = sizeof(remote); + socklen_t addrlen = sizeof(remote); - length = recvfrom(mSocket, buffer, sizeof(buffer), - MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &len); + int length = recvfrom(mSocket, buffer, sizeof(buffer), + MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &addrlen); // Do we need to check SSRC, sequence, and timestamp? They are not // reliable but at least they can be used to identify duplicates? @@ -433,14 +433,15 @@ void AudioStream::decode(int tick) } length -= offset; if (length >= 0) { - length = mCodec->decode(samples, &buffer[offset], length); + length = mCodec->decode(samples, count, &buffer[offset], length); } if (length > 0 && mFixRemote) { mRemote = remote; mFixRemote = false; } + count = length; } - if (length <= 0) { + if (count <= 0) { LOGV("stream[%d] decoder error", mSocket); return; } @@ -462,7 +463,7 @@ void AudioStream::decode(int tick) // Append to the jitter buffer. int tail = mBufferTail * mSampleRate; - for (int i = 0; i < mSampleCount; ++i) { + for (int i = 0; i < count; ++i) { mBuffer[tail & mBufferMask] = samples[i]; ++tail; } diff --git a/jni/rtp/G711Codec.cpp b/jni/rtp/G711Codec.cpp index a467acf..ef54863 100644 --- a/jni/rtp/G711Codec.cpp +++ b/jni/rtp/G711Codec.cpp @@ -39,7 +39,7 @@ public: return mSampleCount; } int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); + int decode(int16_t *samples, int count, void *payload, int length); private: int mSampleCount; }; @@ -64,9 +64,12 @@ int UlawCodec::encode(void *payload, int16_t *samples) return mSampleCount; } -int UlawCodec::decode(int16_t *samples, void *payload, int length) +int UlawCodec::decode(int16_t *samples, int count, void *payload, int length) { int8_t *ulaws = (int8_t *)payload; + if (length > count) { + length = count; + } for (int i = 0; i < length; ++i) { int ulaw = ~ulaws[i]; int exponent = (ulaw >> 4) & 0x07; @@ -87,7 +90,7 @@ public: return mSampleCount; } int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); + int decode(int16_t *samples, int count, void *payload, int length); private: int mSampleCount; }; @@ -111,9 +114,12 @@ int AlawCodec::encode(void *payload, int16_t *samples) return mSampleCount; } -int AlawCodec::decode(int16_t *samples, void *payload, int length) +int AlawCodec::decode(int16_t *samples, int count, void *payload, int length) { int8_t *alaws = (int8_t *)payload; + if (length > count) { + length = count; + } for (int i = 0; i < length; ++i) { int alaw = alaws[i] ^ 0x55; int exponent = (alaw >> 4) & 0x07; diff --git a/jni/rtp/GsmCodec.cpp b/jni/rtp/GsmCodec.cpp index 8d2286e..61dfdc9 100644 --- a/jni/rtp/GsmCodec.cpp +++ b/jni/rtp/GsmCodec.cpp @@ -44,7 +44,7 @@ public: } int encode(void *payload, int16_t *samples); - int decode(int16_t *samples, void *payload, int length); + int decode(int16_t *samples, int count, void *payload, int length); private: gsm mEncode; @@ -57,13 +57,17 @@ int GsmCodec::encode(void *payload, int16_t *samples) return 33; } -int GsmCodec::decode(int16_t *samples, void *payload, int length) +int GsmCodec::decode(int16_t *samples, int count, void *payload, int length) { - if (length == 33 && - gsm_decode(mDecode, (unsigned char *)payload, samples) == 0) { - return 160; + unsigned char *bytes = (unsigned char *)payload; + int n = 0; + while (n + 160 <= count && length >= 33 && + gsm_decode(mDecode, bytes, &samples[n]) == 0) { + n += 160; + length -= 33; + bytes += 33; } - return -1; + return n; } } // namespace -- cgit v1.2.3 From 7bece4e883db6abfa8e907c43bf031a209c66de9 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 6 Sep 2011 23:45:17 -0700 Subject: RTP: Update parameters for larger packet intervals. Also remove some duplicated code. Change-Id: I64576e5442a962eb4b0dfa83b52a8127567ba597 --- jni/rtp/AudioGroup.cpp | 84 ++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 9b0455c..4955667 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -28,6 +28,7 @@ #include #include +// #define LOG_NDEBUG 0 #define LOG_TAG "AudioGroup" #include #include @@ -62,9 +63,9 @@ int gRandom = -1; // a modulo operation on the index while accessing the array. However modulo can // be expensive on some platforms, such as ARM. Thus we round up the size of the // array to the nearest power of 2 and then use bitwise-and instead of modulo. -// Currently we make it 512ms long and assume packet interval is 40ms or less. -// The first 80ms is the place where samples get mixed. The rest 432ms is the -// real jitter buffer. For a stream at 8000Hz it takes 8192 bytes. These numbers +// Currently we make it 2048ms long and assume packet interval is 50ms or less. +// The first 100ms is the place where samples get mixed. The rest is the real +// jitter buffer. For a stream at 8000Hz it takes 32 kilobytes. These numbers // are chosen by experiments and each of them can be adjusted as needed. // Originally a stream does not send packets when it is receive-only or there is @@ -84,9 +85,11 @@ int gRandom = -1; // + Resampling is not done yet, so streams in one group must use the same rate. // For the first release only 8000Hz is supported. -#define BUFFER_SIZE 512 -#define HISTORY_SIZE 80 -#define MEASURE_PERIOD 2000 +#define BUFFER_SIZE 2048 +#define HISTORY_SIZE 100 +#define MEASURE_BASE 100 +#define MEASURE_PERIOD 5000 +#define DTMF_PERIOD 200 class AudioStream { @@ -278,7 +281,7 @@ void AudioStream::encode(int tick, AudioStream *chain) if (mMode != RECEIVE_ONLY && mDtmfEvent != -1) { int duration = mTimestamp - mDtmfStart; // Make sure duration is reasonable. - if (duration >= 0 && duration < mSampleRate * 100) { + if (duration >= 0 && duration < mSampleRate * DTMF_PERIOD) { duration += mSampleCount; int32_t buffer[4] = { htonl(mDtmfMagic | mSequence), @@ -286,7 +289,7 @@ void AudioStream::encode(int tick, AudioStream *chain) mSsrc, htonl(mDtmfEvent | duration), }; - if (duration >= mSampleRate * 100) { + if (duration >= mSampleRate * DTMF_PERIOD) { buffer[3] |= htonl(1 << 23); mDtmfEvent = -1; } @@ -298,43 +301,39 @@ void AudioStream::encode(int tick, AudioStream *chain) } int32_t buffer[mSampleCount + 3]; - int16_t samples[mSampleCount]; - if (mMode == RECEIVE_ONLY) { - if ((mTick ^ mKeepAlive) >> 10 == 0) { - return; - } - mKeepAlive = mTick; - memset(samples, 0, sizeof(samples)); - } else { + bool data = false; + if (mMode != RECEIVE_ONLY) { // Mix all other streams. - bool mixed = false; memset(buffer, 0, sizeof(buffer)); while (chain) { - if (chain != this && - chain->mix(buffer, tick - mInterval, tick, mSampleRate)) { - mixed = true; + if (chain != this) { + data |= chain->mix(buffer, tick - mInterval, tick, mSampleRate); } chain = chain->mNext; } + } - if (mixed) { - // Saturate into 16 bits. - for (int i = 0; i < mSampleCount; ++i) { - int32_t sample = buffer[i]; - if (sample < -32768) { - sample = -32768; - } - if (sample > 32767) { - sample = 32767; - } - samples[i] = sample; + int16_t samples[mSampleCount]; + if (data) { + // Saturate into 16 bits. + for (int i = 0; i < mSampleCount; ++i) { + int32_t sample = buffer[i]; + if (sample < -32768) { + sample = -32768; } - } else { - if ((mTick ^ mKeepAlive) >> 10 == 0) { - return; + if (sample > 32767) { + sample = 32767; } - mKeepAlive = mTick; - memset(samples, 0, sizeof(samples)); + samples[i] = sample; + } + } else { + if ((mTick ^ mKeepAlive) >> 10 == 0) { + return; + } + mKeepAlive = mTick; + memset(samples, 0, sizeof(samples)); + + if (mMode != RECEIVE_ONLY) { LOGV("stream[%d] no data", mSocket); } } @@ -380,19 +379,16 @@ void AudioStream::decode(int tick) } } - // Adjust the jitter buffer if the latency keeps larger than two times of the - // packet interval in the past two seconds. - int score = mBufferTail - tick - mInterval * 2; - if (mLatencyScore > score) { + // Adjust the jitter buffer if the latency keeps larger than the threshold + // in the measurement period. + int score = mBufferTail - tick - MEASURE_BASE; + if (mLatencyScore > score || mLatencyScore <= 0) { mLatencyScore = score; - } - if (mLatencyScore <= 0) { mLatencyTimer = tick; - mLatencyScore = score; } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); mBufferTail -= mLatencyScore; - mLatencyTimer = tick; + mLatencyScore = -1; } int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; -- cgit v1.2.3 From 37f93394fe5c7cfede3a780214fa99a9e2a69133 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 8 Sep 2011 16:43:50 -0700 Subject: SIP: fix keep-alive measurement and increase the timeout. Bug: 5226511 Change-Id: I1283790581496b1ff4e583a8d9379cdc39f78c20 --- java/com/android/server/sip/SipService.java | 62 ++++++++++++------------ java/com/android/server/sip/SipSessionGroup.java | 4 +- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index c553947..f417ddd 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -442,6 +442,7 @@ public final class SipService extends ISipService.Stub { if (wasConnected) { mLocalIp = null; + stopPortMappingMeasurement(); for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(false); } @@ -457,7 +458,6 @@ public final class SipService extends ISipService.Stub { if (isWifi && (mWifiLock != null)) stopWifiScanner(); } else { mMyWakeLock.reset(); // in case there's a leak - stopPortMappingMeasurement(); if (isWifi && (mWifiLock != null)) startWifiScanner(); } } catch (SipException e) { @@ -784,52 +784,50 @@ public final class SipService extends ISipService.Stub { private static final int PASS_THRESHOLD = 10; private static final int MAX_RETRY_COUNT = 5; private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds + private SipProfile mLocalProfile; private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; private int mMinInterval; private int mMaxInterval; private int mInterval; - private int mPassCount = 0; + private int mPassCount; public IntervalMeasurementProcess(SipProfile localProfile, int minInterval, int maxInterval) { mMaxInterval = maxInterval; mMinInterval = minInterval; - mInterval = (maxInterval + minInterval) / 2; - - // Don't start measurement if the interval is too small - if (mInterval < DEFAULT_KEEPALIVE_INTERVAL) { - Log.w(TAG, "interval is too small; measurement aborted; " - + "maxInterval=" + mMaxInterval); - return; - } else if (checkTermination()) { - Log.w(TAG, "interval is too small; measurement aborted; " - + "interval=[" + mMinInterval + "," + mMaxInterval - + "]"); - return; - } - - try { - mGroup = new SipSessionGroupExt(localProfile, null, null); - // TODO: remove this line once SipWakeupTimer can better handle - // variety of timeout values - mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); - } catch (Exception e) { - Log.w(TAG, "start interval measurement error: " + e); - } + mLocalProfile = localProfile; } public void start() { synchronized (SipService.this) { - Log.d(TAG, "start measurement w interval=" + mInterval); - if (mSession == null) { - mSession = (SipSessionGroup.SipSessionImpl) - mGroup.createSession(null); + if (mSession != null) { + return; } + + mInterval = (mMaxInterval + mMinInterval) / 2; + mPassCount = 0; + + // Don't start measurement if the interval is too small + if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) { + Log.w(TAG, "measurement aborted; interval=[" + + mMinInterval + "," + mMaxInterval + "]"); + return; + } + try { + Log.d(TAG, "start measurement w interval=" + mInterval); + + mGroup = new SipSessionGroupExt(mLocalProfile, null, null); + // TODO: remove this line once SipWakeupTimer can better handle + // variety of timeout values + mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); + + mSession = (SipSessionGroup.SipSessionImpl) + mGroup.createSession(null); mSession.startKeepAliveProcess(mInterval, this); - } catch (SipException e) { - Log.e(TAG, "start()", e); + } catch (Throwable t) { + onError(SipErrorCode.CLIENT_ERROR, t.toString()); } } } @@ -840,6 +838,10 @@ public final class SipService extends ISipService.Stub { mSession.stopKeepAliveProcess(); mSession = null; } + if (mGroup != null) { + mGroup.close(); + mGroup = null; + } mTimer.cancel(this); } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index eb5cce7..06cdaf2 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -100,7 +100,7 @@ class SipSessionGroup implements SipListener { private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds private static final int END_CALL_TIMER = 3; // in seconds - private static final int KEEPALIVE_TIMEOUT = 3; // in seconds + private static final int KEEPALIVE_TIMEOUT = 5; // in seconds private static final int INCALL_KEEPALIVE_INTERVAL = 10; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds @@ -1555,7 +1555,7 @@ class SipSessionGroup implements SipListener { try { sendKeepAlive(); } catch (Throwable t) { - Log.w(TAG, "keepalive error: " + ": " + Log.w(TAG, "keepalive error: " + mLocalProfile.getUriString(), getRootCause(t)); // It's possible that the keepalive process is being stopped // during session.sendKeepAlive() so need to check mRunning -- cgit v1.2.3 From 79d4e0ecfe1bc8d369ffcdfb01dd1900b3ea10d5 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 27 Sep 2011 16:39:38 -0700 Subject: SipService: handle connectivity changes correctly. This patch assumes that for the same network type, there MUST be a DISCONNECTED between two CONNECTEDs. Also removes the Wi-Fi scanning since the framework already handles this when a WifiLock is held. Bug: 4283795 Change-Id: I08481e70c651cffcbb516c8cc6584c919078fa4f --- java/com/android/server/sip/SipService.java | 363 +++++++--------------------- 1 file changed, 89 insertions(+), 274 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index f417ddd..119ed54 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -81,10 +81,8 @@ public final class SipService extends ISipService.Stub { private String mNetworkType; private boolean mConnected; private SipWakeupTimer mTimer; - private WifiScanProcess mWifiScanProcess; private WifiManager.WifiLock mWifiLock; - private boolean mWifiOnly; - private BroadcastReceiver mWifiStateReceiver = null; + private boolean mSipOnWifiOnly; private IntervalMeasurementProcess mIntervalMeasurementProcess; @@ -99,7 +97,6 @@ public final class SipService extends ISipService.Stub { new HashMap(); private ConnectivityReceiver mConnectivityReceiver; - private boolean mWifiEnabled; private SipWakeLock mMyWakeLock; private int mKeepAliveInterval; private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; @@ -120,55 +117,17 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, " service started!"); mContext = context; mConnectivityReceiver = new ConnectivityReceiver(); + + mWifiLock = ((WifiManager) + context.getSystemService(Context.WIFI_SERVICE)) + .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); + mWifiLock.setReferenceCounted(false); + mSipOnWifiOnly = SipManager.isSipWifiOnly(context); + mMyWakeLock = new SipWakeLock((PowerManager) context.getSystemService(Context.POWER_SERVICE)); mTimer = new SipWakeupTimer(context, mExecutor); - mWifiOnly = SipManager.isSipWifiOnly(context); - } - - private BroadcastReceiver createWifiBroadcastReceiver() { - return new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { - int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN); - synchronized (SipService.this) { - switch (state) { - case WifiManager.WIFI_STATE_ENABLED: - mWifiEnabled = true; - if (anyOpenedToReceiveCalls()) grabWifiLock(); - break; - case WifiManager.WIFI_STATE_DISABLED: - mWifiEnabled = false; - releaseWifiLock(); - break; - } - } - } - } - }; - }; - - private void registerReceivers() { - mContext.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - if (SipManager.isSipWifiOnly(mContext)) { - mWifiStateReceiver = createWifiBroadcastReceiver(); - mContext.registerReceiver(mWifiStateReceiver, - new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)); - } - if (DEBUG) Log.d(TAG, " +++ register receivers"); - } - - private void unregisterReceivers() { - mContext.unregisterReceiver(mConnectivityReceiver); - if (SipManager.isSipWifiOnly(mContext)) { - mContext.unregisterReceiver(mWifiStateReceiver); - } - if (DEBUG) Log.d(TAG, " --- unregister receivers"); } public synchronized SipProfile[] getListOfProfiles() { @@ -218,7 +177,6 @@ public final class SipService extends ISipService.Stub { if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); - if (mWifiEnabled) grabWifiLock(); } } catch (SipException e) { Log.e(TAG, "openToReceiveCalls()", e); @@ -254,10 +212,9 @@ public final class SipService extends ISipService.Stub { group.close(); if (!anyOpenedToReceiveCalls()) { - releaseWifiLock(); + unregisterReceivers(); mMyWakeLock.reset(); // in case there's leak } - if (mSipGroups.isEmpty()) unregisterReceivers(); } public synchronized boolean isOpened(String localProfileUri) { @@ -388,83 +345,6 @@ public final class SipService extends ISipService.Stub { return false; } - private void grabWifiLock() { - if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "acquire wifi lock"); - mWifiLock = ((WifiManager) - mContext.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - mWifiLock.acquire(); - if (!mConnected) startWifiScanner(); - } - } - - private void releaseWifiLock() { - if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "release wifi lock"); - mWifiLock.release(); - mWifiLock = null; - stopWifiScanner(); - } - } - - private synchronized void startWifiScanner() { - if (mWifiScanProcess == null) { - mWifiScanProcess = new WifiScanProcess(); - } - mWifiScanProcess.start(); - } - - private synchronized void stopWifiScanner() { - if (mWifiScanProcess != null) { - mWifiScanProcess.stop(); - } - } - - private synchronized void onConnectivityChanged( - String type, boolean connected) { - if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " - + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") - + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); - - boolean sameType = type.equals(mNetworkType); - if (!sameType && !connected) return; - - boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); - boolean isWifi = "WIFI".equalsIgnoreCase(type); - boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); - boolean wifiOn = isWifi && connected; - - try { - boolean wasConnected = mConnected; - mNetworkType = type; - mConnected = connected; - - if (wasConnected) { - mLocalIp = null; - stopPortMappingMeasurement(); - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(false); - } - } - - if (connected) { - mLocalIp = determineLocalIp(); - mKeepAliveInterval = -1; - mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(true); - } - if (isWifi && (mWifiLock != null)) stopWifiScanner(); - } else { - mMyWakeLock.reset(); // in case there's a leak - if (isWifi && (mWifiLock != null)) startWifiScanner(); - } - } catch (SipException e) { - Log.e(TAG, "onConnectivityChanged()", e); - } - } - private void stopPortMappingMeasurement() { if (mIntervalMeasurementProcess != null) { mIntervalMeasurementProcess.stop(); @@ -747,36 +627,6 @@ public final class SipService extends ISipService.Stub { } } - private class WifiScanProcess implements Runnable { - private static final String TAG = "\\WIFI_SCAN/"; - private static final int INTERVAL = 60; - private boolean mRunning = false; - - private WifiManager mWifiManager; - - public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(INTERVAL * 1000, this); - } - - WifiScanProcess() { - mWifiManager = (WifiManager) - mContext.getSystemService(Context.WIFI_SERVICE); - } - - public void run() { - // scan and associate now - if (DEBUGV) Log.v(TAG, "just wake up here for wifi scanning..."); - mWifiManager.startScanActive(); - } - - public void stop() { - mRunning = false; - mTimer.cancel(this); - } - } - private class IntervalMeasurementProcess implements Runnable, SipSessionGroup.KeepAliveProcessCallback { private static final String TAG = "SipKeepAliveInterval"; @@ -1254,138 +1104,103 @@ public final class SipService extends ISipService.Stub { } private class ConnectivityReceiver extends BroadcastReceiver { - private Timer mTimer = new Timer(); - private MyTimerTask mTask; - @Override - public void onReceive(final Context context, final Intent intent) { - // Run the handler in MyExecutor to be protected by wake lock - mExecutor.execute(new Runnable() { - public void run() { - onReceiveInternal(context, intent); - } - }); - } - - private void onReceiveInternal(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - Bundle b = intent.getExtras(); - if (b != null) { - NetworkInfo netInfo = (NetworkInfo) - b.get(ConnectivityManager.EXTRA_NETWORK_INFO); - String type = netInfo.getTypeName(); - NetworkInfo.State state = netInfo.getState(); - - if (mWifiOnly && (netInfo.getType() != - ConnectivityManager.TYPE_WIFI)) { - if (DEBUG) { - Log.d(TAG, "Wifi only, other connectivity ignored: " - + type); - } - return; - } + public void onReceive(Context context, Intent intent) { + Bundle bundle = intent.getExtras(); + if (bundle != null) { + final NetworkInfo info = (NetworkInfo) + bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO); - NetworkInfo activeNetInfo = getActiveNetworkInfo(); - if (DEBUG) { - if (activeNetInfo != null) { - Log.d(TAG, "active network: " - + activeNetInfo.getTypeName() - + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) - ? " CONNECTED" : " DISCONNECTED")); - } else { - Log.d(TAG, "active network: null"); - } - } - if ((state == NetworkInfo.State.CONNECTED) - && (activeNetInfo != null) - && (activeNetInfo.getType() != netInfo.getType())) { - if (DEBUG) Log.d(TAG, "ignore connect event: " + type - + ", active: " + activeNetInfo.getTypeName()); - return; - } - - if (state == NetworkInfo.State.CONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); - onChanged(type, true); - } else if (state == NetworkInfo.State.DISCONNECTED) { - if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); - onChanged(type, false); - } else { - if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " - + state + " " + type); + // Run the handler in MyExecutor to be protected by wake lock + mExecutor.execute(new Runnable() { + public void run() { + onConnectivityChanged(info); } - } + }); } } + } + + private void registerReceivers() { + mContext.registerReceiver(mConnectivityReceiver, + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + if (DEBUG) Log.d(TAG, " +++ register receivers"); + } - private NetworkInfo getActiveNetworkInfo() { + private void unregisterReceivers() { + mContext.unregisterReceiver(mConnectivityReceiver); + if (DEBUG) Log.d(TAG, " --- unregister receivers"); + + // Reset variables maintained by ConnectivityReceiver. + mWifiLock.release(); + mConnected = false; + } + + private synchronized void onConnectivityChanged(NetworkInfo info) { + // We only care about the default network, and getActiveNetworkInfo() + // is the only way to distinguish them. However, as broadcasts are + // delivered asynchronously, we might miss DISCONNECTED events from + // getActiveNetworkInfo(), which is critical to our SIP stack. To + // solve this, if it is a DISCONNECTED event to our current network, + // respect it. Otherwise get a new one from getActiveNetworkInfo(). + if (info == null || info.isConnected() || + !info.getTypeName().equals(mNetworkType)) { ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - return cm.getActiveNetworkInfo(); + info = cm.getActiveNetworkInfo(); } - private void onChanged(String type, boolean connected) { - synchronized (SipService.this) { - // When turning on WIFI, it needs some time for network - // connectivity to get stabile so we defer good news (because - // we want to skip the interim ones) but deliver bad news - // immediately - if (connected) { - if (mTask != null) { - mTask.cancel(); - mMyWakeLock.release(mTask); - } - mTask = new MyTimerTask(type, connected); - mTimer.schedule(mTask, 2 * 1000L); - // hold wakup lock so that we can finish changes before the - // device goes to sleep - mMyWakeLock.acquire(mTask); - } else { - if ((mTask != null) && mTask.mNetworkType.equals(type)) { - mTask.cancel(); - mMyWakeLock.release(mTask); - } - onConnectivityChanged(type, false); - } - } - } + // Some devices limit SIP on Wi-Fi. In this case, if we are not on + // Wi-Fi, treat it as a DISCONNECTED event. + boolean connected = (info != null && info.isConnected() && + (!mSipOnWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI)); + String networkType = connected ? info.getTypeName() : "null"; - private class MyTimerTask extends TimerTask { - private boolean mConnected; - private String mNetworkType; + // Ignore the event if the current active network is not changed. + if (connected == mConnected && networkType.equals(mNetworkType)) { + return; + } + if (DEBUG) { + Log.d(TAG, "onConnectivityChanged(): " + mNetworkType + + " -> " + networkType); + } - public MyTimerTask(String type, boolean connected) { - mNetworkType = type; - mConnected = connected; + try { + if (mConnected) { + mLocalIp = null; + stopPortMappingMeasurement(); + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(false); + } } - // timeout handler - @Override - public void run() { - // delegate to mExecutor - mExecutor.execute(new Runnable() { - public void run() { - realRun(); - } - }); - } + mConnected = connected; + mNetworkType = networkType; - private void realRun() { - synchronized (SipService.this) { - if (mTask != this) { - Log.w(TAG, " unexpected task: " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - mMyWakeLock.release(this); - return; - } - mTask = null; - if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType - + (mConnected ? " CONNECTED" : "DISCONNECTED")); - onConnectivityChanged(mNetworkType, mConnected); - mMyWakeLock.release(this); + if (connected) { + mLocalIp = determineLocalIp(); + mKeepAliveInterval = -1; + mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(true); } + + // If we are on Wi-Fi, grab the WifiLock. Otherwise release it. + if (info.getType() == ConnectivityManager.TYPE_WIFI) { + mWifiLock.acquire(); + } else { + mWifiLock.release(); + } + } else { + // Always grab the WifiLock when we are disconnected, so the + // system will keep trying to reconnect. We will release it + // if we eventually connect via something else. + mWifiLock.acquire(); + + mMyWakeLock.reset(); // in case there's a leak } + } catch (SipException e) { + Log.e(TAG, "onConnectivityChanged()", e); } } -- cgit v1.2.3 From a0ee529927ab5cb2762994fc865df09179bdcda8 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Thu, 20 Oct 2011 11:56:00 +0100 Subject: Rename (IF_)LOGV(_IF) to (IF_)ALOGV(_IF) DO NOT MERGE See https://android-git.corp.google.com/g/#/c/143865 Bug: 5449033 Change-Id: I0122812ed6ff6f5b59fe4a43ab8bff0577adde0a --- jni/rtp/AudioGroup.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 459756d..6f8a232 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -269,7 +269,7 @@ void AudioStream::encode(int tick, AudioStream *chain) mTick += skipped * mInterval; mSequence += skipped; mTimestamp += skipped * mSampleCount; - LOGV("stream[%d] skips %d packets", mSocket, skipped); + ALOGV("stream[%d] skips %d packets", mSocket, skipped); } tick = mTick; @@ -334,7 +334,7 @@ void AudioStream::encode(int tick, AudioStream *chain) memset(samples, 0, sizeof(samples)); if (mMode != RECEIVE_ONLY) { - LOGV("stream[%d] no data", mSocket); + ALOGV("stream[%d] no data", mSocket); } } @@ -350,7 +350,7 @@ void AudioStream::encode(int tick, AudioStream *chain) buffer[2] = mSsrc; int length = mCodec->encode(&buffer[3], samples); if (length <= 0) { - LOGV("stream[%d] encoder error", mSocket); + ALOGV("stream[%d] encoder error", mSocket); return; } sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, @@ -386,7 +386,7 @@ void AudioStream::decode(int tick) mLatencyScore = score; mLatencyTimer = tick; } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { - LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); mBufferTail -= mLatencyScore; mLatencyScore = -1; } @@ -394,7 +394,7 @@ void AudioStream::decode(int tick) int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; if (count < mSampleCount) { // Buffer overflow. Drop the packet. - LOGV("stream[%d] buffer overflow", mSocket); + ALOGV("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); return; } @@ -417,7 +417,7 @@ void AudioStream::decode(int tick) // reliable but at least they can be used to identify duplicates? if (length < 12 || length > (int)sizeof(buffer) || (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { - LOGV("stream[%d] malformed packet", mSocket); + ALOGV("stream[%d] malformed packet", mSocket); return; } int offset = 12 + ((buffer[0] & 0x0F) << 2); @@ -438,13 +438,13 @@ void AudioStream::decode(int tick) count = length; } if (count <= 0) { - LOGV("stream[%d] decoder error", mSocket); + ALOGV("stream[%d] decoder error", mSocket); return; } if (tick - mBufferTail > 0) { // Buffer underrun. Reset the jitter buffer. - LOGV("stream[%d] buffer underrun", mSocket); + ALOGV("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { mBufferHead = tick + mInterval; mBufferTail = mBufferHead; @@ -913,7 +913,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (mode != MUTED) { if (echo != NULL) { - LOGV("echo->run()"); + ALOGV("echo->run()"); echo->run(output, input); } send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); -- cgit v1.2.3 From 220df86e1d74070a3af04c33ad23b21765f00f2b Mon Sep 17 00:00:00 2001 From: Steve Block Date: Wed, 26 Oct 2011 04:48:16 -0700 Subject: am 71f2cf11: (-s ours) Rename (IF_)LOGV(_IF) to (IF_)ALOGV(_IF) DO NOT MERGE * commit '71f2cf116aab893e224056c38ab146bd1538dd3e': Rename (IF_)LOGV(_IF) to (IF_)ALOGV(_IF) DO NOT MERGE --- jni/rtp/AudioGroup.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 6f8a232..459756d 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -269,7 +269,7 @@ void AudioStream::encode(int tick, AudioStream *chain) mTick += skipped * mInterval; mSequence += skipped; mTimestamp += skipped * mSampleCount; - ALOGV("stream[%d] skips %d packets", mSocket, skipped); + LOGV("stream[%d] skips %d packets", mSocket, skipped); } tick = mTick; @@ -334,7 +334,7 @@ void AudioStream::encode(int tick, AudioStream *chain) memset(samples, 0, sizeof(samples)); if (mMode != RECEIVE_ONLY) { - ALOGV("stream[%d] no data", mSocket); + LOGV("stream[%d] no data", mSocket); } } @@ -350,7 +350,7 @@ void AudioStream::encode(int tick, AudioStream *chain) buffer[2] = mSsrc; int length = mCodec->encode(&buffer[3], samples); if (length <= 0) { - ALOGV("stream[%d] encoder error", mSocket); + LOGV("stream[%d] encoder error", mSocket); return; } sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, @@ -386,7 +386,7 @@ void AudioStream::decode(int tick) mLatencyScore = score; mLatencyTimer = tick; } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { - ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); mBufferTail -= mLatencyScore; mLatencyScore = -1; } @@ -394,7 +394,7 @@ void AudioStream::decode(int tick) int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; if (count < mSampleCount) { // Buffer overflow. Drop the packet. - ALOGV("stream[%d] buffer overflow", mSocket); + LOGV("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); return; } @@ -417,7 +417,7 @@ void AudioStream::decode(int tick) // reliable but at least they can be used to identify duplicates? if (length < 12 || length > (int)sizeof(buffer) || (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { - ALOGV("stream[%d] malformed packet", mSocket); + LOGV("stream[%d] malformed packet", mSocket); return; } int offset = 12 + ((buffer[0] & 0x0F) << 2); @@ -438,13 +438,13 @@ void AudioStream::decode(int tick) count = length; } if (count <= 0) { - ALOGV("stream[%d] decoder error", mSocket); + LOGV("stream[%d] decoder error", mSocket); return; } if (tick - mBufferTail > 0) { // Buffer underrun. Reset the jitter buffer. - ALOGV("stream[%d] buffer underrun", mSocket); + LOGV("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { mBufferHead = tick + mInterval; mBufferTail = mBufferHead; @@ -913,7 +913,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (mode != MUTED) { if (echo != NULL) { - ALOGV("echo->run()"); + LOGV("echo->run()"); echo->run(output, input); } send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); -- cgit v1.2.3 From 85caf063c027ba0d1defeaff8850e12b6428c4f7 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Fri, 18 Nov 2011 16:57:21 -0800 Subject: SIP: turn off verbose logs. Bug: 5616713 Change-Id: Iaf2e6878731d10d7f4f2a7cd8af71f4517780642 --- java/com/android/server/sip/SipHelper.java | 2 +- java/com/android/server/sip/SipService.java | 29 ++++++++-------- java/com/android/server/sip/SipSessionGroup.java | 44 ++++++++++++++---------- java/com/android/server/sip/SipWakeLock.java | 12 +++---- 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index dc628e0..113f007 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -73,7 +73,7 @@ import javax.sip.message.Response; */ class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final boolean DEBUG_PING = false; private SipStack mSipStack; diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 119ed54..38a683e 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -68,8 +68,7 @@ import javax.sip.SipException; */ public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; - static final boolean DEBUGV = false; - static final boolean DEBUG = true; + static final boolean DEBUG = false; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; @@ -581,7 +580,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRinging(ISipSession s, SipProfile caller, String sessionDescription) { - if (DEBUGV) Log.d(TAG, "<<<<< onRinging()"); + if (DEBUG) Log.d(TAG, "<<<<< onRinging()"); SipSessionGroup.SipSessionImpl session = (SipSessionGroup.SipSessionImpl) s; synchronized (SipService.this) { @@ -778,7 +777,6 @@ public final class SipService extends ISipService.Stub { private void restartLater() { synchronized (SipService.this) { int interval = NAT_MEASUREMENT_RETRY_INTERVAL; - Log.d(TAG, "Retry measurement " + interval + "s later."); mTimer.cancel(this); mTimer.set(interval * 1000, this); } @@ -788,7 +786,7 @@ public final class SipService extends ISipService.Stub { private class AutoRegistrationProcess extends SipSessionAdapter implements Runnable, SipSessionGroup.KeepAliveProcessCallback { private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10; - private String TAG = "SipAudoReg"; + private String TAG = "SipAutoReg"; private SipSessionGroup.SipSessionImpl mSession; private SipSessionGroup.SipSessionImpl mKeepAliveSession; @@ -820,13 +818,12 @@ public final class SipService extends ISipService.Stub { // in registration to avoid adding duplicate entries to server mMyWakeLock.acquire(mSession); mSession.unregister(); - if (DEBUG) TAG = mSession.getLocalProfile().getUriString(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess"); + TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString(); } } private void startKeepAliveProcess(int interval) { - Log.d(TAG, "start keepalive w interval=" + interval); + if (DEBUG) Log.d(TAG, "start keepalive w interval=" + interval); if (mKeepAliveSession == null) { mKeepAliveSession = mSession.duplicate(); } else { @@ -864,9 +861,11 @@ public final class SipService extends ISipService.Stub { mKeepAliveSuccessCount = 0; } } else { - Log.i(TAG, "keep keepalive going with interval " - + interval + ", past success count=" - + mKeepAliveSuccessCount); + if (DEBUG) { + Log.i(TAG, "keep keepalive going with interval " + + interval + ", past success count=" + + mKeepAliveSuccessCount); + } mKeepAliveSuccessCount /= 2; } } else { @@ -894,7 +893,9 @@ public final class SipService extends ISipService.Stub { // SipSessionGroup.KeepAliveProcessCallback @Override public void onError(int errorCode, String description) { - Log.e(TAG, "keepalive error: " + description); + if (DEBUG) { + Log.e(TAG, "keepalive error: " + description); + } onResponse(true); // re-register immediately } @@ -917,7 +918,7 @@ public final class SipService extends ISipService.Stub { public void onKeepAliveIntervalChanged() { if (mKeepAliveSession != null) { int newInterval = getKeepAliveInterval(); - if (DEBUGV) { + if (DEBUG) { Log.v(TAG, "restart keepalive w interval=" + newInterval); } mKeepAliveSuccessCount = 0; @@ -987,7 +988,7 @@ public final class SipService extends ISipService.Stub { } private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); + Log.d(TAG, "Refresh registration " + duration + "s later."); mTimer.cancel(this); mTimer.set(duration * 1000, this); } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 06cdaf2..877a0a4 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -89,8 +89,8 @@ import javax.sip.message.Response; */ class SipSessionGroup implements SipListener { private static final String TAG = "SipSession"; - private static final boolean DEBUG = true; - private static final boolean DEBUG_PING = DEBUG && false; + private static final boolean DEBUG = false; + private static final boolean DEBUG_PING = false; private static final String ANONYMOUS = "anonymous"; // Limit the size of thread pool to 1 for the order issue when the phone is // waken up from sleep and there are many packets to be processed in the SIP @@ -205,7 +205,9 @@ class SipSessionGroup implements SipListener { } synchronized void resetExternalAddress() { - Log.d(TAG, " reset external addr on " + mSipStack); + if (DEBUG) { + Log.d(TAG, " reset external addr on " + mSipStack); + } mExternalIp = null; mExternalPort = 0; } @@ -362,7 +364,7 @@ class SipSessionGroup implements SipListener { + SipSession.State.toString(session.mState)); } } catch (Throwable e) { - Log.w(TAG, "event process error: " + event, e); + Log.w(TAG, "event process error: " + event, getRootCause(e)); session.onError(e); } } @@ -393,9 +395,20 @@ class SipSessionGroup implements SipListener { if ((rport > 0) && (externalIp != null)) { mExternalIp = externalIp; mExternalPort = rport; - Log.d(TAG, " got external addr " + externalIp + ":" + rport - + " on " + mSipStack); + if (DEBUG) { + Log.d(TAG, " got external addr " + externalIp + ":" + rport + + " on " + mSipStack); + } + } + } + + private Throwable getRootCause(Throwable exception) { + Throwable cause = exception.getCause(); + while (cause != null) { + exception = cause; + cause = exception.getCause(); } + return exception; } private SipSessionImpl createNewSession(RequestEvent event, @@ -890,7 +903,9 @@ class SipSessionGroup implements SipListener { if (expires != null && time < expires.getExpires()) { time = expires.getExpires(); } - Log.v(TAG, "Expiry time = " + time); + if (DEBUG) { + Log.v(TAG, "Expiry time = " + time); + } return time; } @@ -1409,15 +1424,6 @@ class SipSessionGroup implements SipListener { } } - private Throwable getRootCause(Throwable exception) { - Throwable cause = exception.getCause(); - while (cause != null) { - exception = cause; - cause = exception.getCause(); - } - return exception; - } - private int getErrorCode(Throwable exception) { String message = exception.getMessage(); if (exception instanceof UnknownHostException) { @@ -1555,8 +1561,10 @@ class SipSessionGroup implements SipListener { try { sendKeepAlive(); } catch (Throwable t) { - Log.w(TAG, "keepalive error: " - + mLocalProfile.getUriString(), getRootCause(t)); + if (DEBUG) { + Log.w(TAG, "keepalive error: " + + mLocalProfile.getUriString(), getRootCause(t)); + } // It's possible that the keepalive process is being stopped // during session.sendKeepAlive() so need to check mRunning // again here. diff --git a/java/com/android/server/sip/SipWakeLock.java b/java/com/android/server/sip/SipWakeLock.java index 52bc094..0c4d14c 100644 --- a/java/com/android/server/sip/SipWakeLock.java +++ b/java/com/android/server/sip/SipWakeLock.java @@ -22,8 +22,8 @@ import android.util.Log; import java.util.HashSet; class SipWakeLock { - private static final boolean DEBUGV = SipService.DEBUGV; - private static final String TAG = SipService.TAG; + private static final boolean DEBUG = false; + private static final String TAG = "SipWakeLock"; private PowerManager mPowerManager; private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mTimerWakeLock; @@ -34,9 +34,9 @@ class SipWakeLock { } synchronized void reset() { + if (DEBUG) Log.v(TAG, "reset count=" + mHolders.size()); mHolders.clear(); release(null); - if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock"); } synchronized void acquire(long timeout) { @@ -55,8 +55,7 @@ class SipWakeLock { PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); } if (!mWakeLock.isHeld()) mWakeLock.acquire(); - if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count=" - + mHolders.size()); + if (DEBUG) Log.v(TAG, "acquire count=" + mHolders.size()); } synchronized void release(Object holder) { @@ -65,7 +64,6 @@ class SipWakeLock { && mWakeLock.isHeld()) { mWakeLock.release(); } - if (DEBUGV) Log.v(TAG, "release wakelock: holder count=" - + mHolders.size()); + if (DEBUG) Log.v(TAG, "release count=" + mHolders.size()); } } -- cgit v1.2.3 From b4074803f775e5a4616287804f3405fd17eecab3 Mon Sep 17 00:00:00 2001 From: Joe Fernandez Date: Tue, 20 Dec 2011 10:38:34 -0800 Subject: docs: Add developer guide cross-references, Project ACRE, round 4 Change-Id: I1b43414aaec8ea217b39a0d780c80a25409d0991 --- java/android/net/sip/SipAudioCall.java | 9 ++++++++- java/android/net/sip/SipManager.java | 9 ++++++++- java/android/net/sip/SipProfile.java | 7 +++++++ java/android/net/sip/package.html | 8 +++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index fcdbd2c..1d67055 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -44,13 +44,20 @@ import java.util.Map; * *

    Note: Using this class require the * {@link android.Manifest.permission#INTERNET} and - * {@link android.Manifest.permission#USE_SIP} permissions.

    In addition, {@link + * {@link android.Manifest.permission#USE_SIP} permissions. In addition, {@link * #startAudio} requires the * {@link android.Manifest.permission#RECORD_AUDIO}, * {@link android.Manifest.permission#ACCESS_WIFI_STATE}, and * {@link android.Manifest.permission#WAKE_LOCK} permissions; and {@link #setSpeakerMode * setSpeakerMode()} requires the * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.

    + * + *
    + *

    Developer Guides

    + *

    For more information about using SIP, read the + * Session Initiation Protocol + * developer guide.

    + *
    */ public class SipAudioCall { private static final String TAG = SipAudioCall.class.getSimpleName(); diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index cd0b5c4..74c3672 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -49,9 +49,16 @@ import java.text.ParseException; * SIP. You should always call {@link android.net.sip.SipManager#isVoipSupported * isVoipSupported()} to verify that the device supports VOIP calling and {@link * android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports - * the SIP APIs.

    Your application must also request the {@link + * the SIP APIs. Your application must also request the {@link * android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP} * permissions.

    + * + *
    + *

    Developer Guides

    + *

    For more information about using SIP, read the + * Session Initiation Protocol + * developer guide.

    + *
    */ public class SipManager { /** diff --git a/java/android/net/sip/SipProfile.java b/java/android/net/sip/SipProfile.java index 34d91dd..0ef754c 100644 --- a/java/android/net/sip/SipProfile.java +++ b/java/android/net/sip/SipProfile.java @@ -37,6 +37,13 @@ import javax.sip.address.URI; *

    You can create a {@link SipProfile} using {@link * SipProfile.Builder}. You can also retrieve one from a {@link SipSession}, using {@link * SipSession#getLocalProfile} and {@link SipSession#getPeerProfile}.

    + * + *
    + *

    Developer Guides

    + *

    For more information about using SIP, read the + * Session Initiation Protocol + * developer guide.

    + *
    */ public class SipProfile implements Parcelable, Serializable, Cloneable { private static final long serialVersionUID = 1L; diff --git a/java/android/net/sip/package.html b/java/android/net/sip/package.html index 790656b..eb683d0 100644 --- a/java/android/net/sip/package.html +++ b/java/android/net/sip/package.html @@ -3,6 +3,11 @@

    Provides access to Session Initiation Protocol (SIP) functionality, such as making and answering VOIP calls using SIP.

    +

    For more information, see the +Session Initiation Protocol +developer guide.

    +{@more} +

    To get started, you need to get an instance of the {@link android.net.sip.SipManager} by calling {@link android.net.sip.SipManager#newInstance newInstance()}.

    @@ -31,9 +36,10 @@ Not all Android-powered devices support VOIP functionality with SIP. Before perf activity, you should call {@link android.net.sip.SipManager#isVoipSupported isVoipSupported()} to verify that the device supports VOIP calling and {@link android.net.sip.SipManager#isApiSupported isApiSupported()} to verify that the device supports the -SIP APIs.

    +SIP APIs. Your application must also request the {@link android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP} permissions in order to use the SIP APIs.

    + \ No newline at end of file -- cgit v1.2.3 From 7d803d942453ed2139bb590aba4c4fe025529a8f Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 27 Dec 2011 17:29:35 -0800 Subject: SipService: grab Wi-Fi lock only when necessary. Change-Id: Ie432049156e70b6748426b959b653f21bfc504a1 --- java/com/android/server/sip/SipService.java | 87 ++++++++++++++--------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 38a683e..97afc81 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -77,8 +77,7 @@ public final class SipService extends ISipService.Stub { private Context mContext; private String mLocalIp; - private String mNetworkType; - private boolean mConnected; + private int mNetworkType = -1; private SipWakeupTimer mTimer; private WifiManager.WifiLock mWifiLock; private boolean mSipOnWifiOnly; @@ -147,9 +146,7 @@ public final class SipService extends ISipService.Stub { android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); try { - boolean addingFirstProfile = mSipGroups.isEmpty(); createGroup(localProfile); - if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); } catch (SipException e) { Log.e(TAG, "openToMakeCalls()", e); // TODO: how to send the exception back @@ -170,12 +167,11 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + incomingCallPendingIntent + ": " + listener); try { - boolean addingFirstProfile = mSipGroups.isEmpty(); SipSessionGroupExt group = createGroup(localProfile, incomingCallPendingIntent, listener); - if (addingFirstProfile && !mSipGroups.isEmpty()) registerReceivers(); if (localProfile.getAutoRegistration()) { group.openToReceiveCalls(); + updateWakeLocks(); } } catch (SipException e) { Log.e(TAG, "openToReceiveCalls()", e); @@ -210,10 +206,7 @@ public final class SipService extends ISipService.Stub { notifyProfileRemoved(group.getLocalProfile()); group.close(); - if (!anyOpenedToReceiveCalls()) { - unregisterReceivers(); - mMyWakeLock.reset(); // in case there's leak - } + updateWakeLocks(); } public synchronized boolean isOpened(String localProfileUri) { @@ -260,7 +253,7 @@ public final class SipService extends ISipService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); - if (!mConnected) return null; + if (mNetworkType == -1) return null; try { SipSessionGroupExt group = createGroup(localProfile); return group.createSession(listener); @@ -328,6 +321,9 @@ public final class SipService extends ISipService.Stub { Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); mContext.sendBroadcast(intent); + if (mSipGroups.size() == 1) { + registerReceivers(); + } } private void notifyProfileRemoved(SipProfile localProfile) { @@ -335,13 +331,9 @@ public final class SipService extends ISipService.Stub { Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); mContext.sendBroadcast(intent); - } - - private boolean anyOpenedToReceiveCalls() { - for (SipSessionGroupExt group : mSipGroups.values()) { - if (group.isOpenedToReceiveCalls()) return true; + if (mSipGroups.size() == 0) { + unregisterReceivers(); } - return false; } private void stopPortMappingMeasurement() { @@ -526,7 +518,7 @@ public final class SipService extends ISipService.Stub { public void openToReceiveCalls() throws SipException { mOpenedToReceiveCalls = true; - if (mConnected) { + if (mNetworkType != -1) { mSipGroup.openToReceiveCalls(this); mAutoRegistration.start(mSipGroup); } @@ -905,7 +897,7 @@ public final class SipService extends ISipService.Stub { mMyWakeLock.release(mSession); if (mSession != null) { mSession.setListener(null); - if (mConnected && mRegistered) mSession.unregister(); + if (mNetworkType != -1 && mRegistered) mSession.unregister(); } mTimer.cancel(this); @@ -948,7 +940,7 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationFailed(mSession, mErrorCode, mErrorMessage); } - } else if (!mConnected) { + } else if (mNetworkType == -1) { mProxy.onRegistrationFailed(mSession, SipErrorCode.DATA_CONNECTION_LOST, "no data connection"); @@ -980,7 +972,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; if (DEBUG) Log.d(TAG, "registering"); - if (mConnected) { + if (mNetworkType != -1) { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); } @@ -1134,7 +1126,25 @@ public final class SipService extends ISipService.Stub { // Reset variables maintained by ConnectivityReceiver. mWifiLock.release(); - mConnected = false; + mNetworkType = -1; + } + + private void updateWakeLocks() { + for (SipSessionGroupExt group : mSipGroups.values()) { + if (group.isOpenedToReceiveCalls()) { + // Also grab the WifiLock when we are disconnected, so the + // system will keep trying to reconnect. It will be released + // when the system eventually connects to something else. + if (mNetworkType == ConnectivityManager.TYPE_WIFI || mNetworkType == -1) { + mWifiLock.acquire(); + } else { + mWifiLock.release(); + } + return; + } + } + mWifiLock.release(); + mMyWakeLock.reset(); // in case there's a leak } private synchronized void onConnectivityChanged(NetworkInfo info) { @@ -1144,8 +1154,7 @@ public final class SipService extends ISipService.Stub { // getActiveNetworkInfo(), which is critical to our SIP stack. To // solve this, if it is a DISCONNECTED event to our current network, // respect it. Otherwise get a new one from getActiveNetworkInfo(). - if (info == null || info.isConnected() || - !info.getTypeName().equals(mNetworkType)) { + if (info == null || info.isConnected() || info.getType() != mNetworkType) { ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); info = cm.getActiveNetworkInfo(); @@ -1153,12 +1162,13 @@ public final class SipService extends ISipService.Stub { // Some devices limit SIP on Wi-Fi. In this case, if we are not on // Wi-Fi, treat it as a DISCONNECTED event. - boolean connected = (info != null && info.isConnected() && - (!mSipOnWifiOnly || info.getType() == ConnectivityManager.TYPE_WIFI)); - String networkType = connected ? info.getTypeName() : "null"; + int networkType = (info != null && info.isConnected()) ? info.getType() : -1; + if (mSipOnWifiOnly && networkType != ConnectivityManager.TYPE_WIFI) { + networkType = -1; + } // Ignore the event if the current active network is not changed. - if (connected == mConnected && networkType.equals(mNetworkType)) { + if (mNetworkType == networkType) { return; } if (DEBUG) { @@ -1167,39 +1177,24 @@ public final class SipService extends ISipService.Stub { } try { - if (mConnected) { + if (mNetworkType != -1) { mLocalIp = null; stopPortMappingMeasurement(); for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(false); } } - - mConnected = connected; mNetworkType = networkType; - if (connected) { + if (mNetworkType != -1) { mLocalIp = determineLocalIp(); mKeepAliveInterval = -1; mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; for (SipSessionGroupExt group : mSipGroups.values()) { group.onConnectivityChanged(true); } - - // If we are on Wi-Fi, grab the WifiLock. Otherwise release it. - if (info.getType() == ConnectivityManager.TYPE_WIFI) { - mWifiLock.acquire(); - } else { - mWifiLock.release(); - } - } else { - // Always grab the WifiLock when we are disconnected, so the - // system will keep trying to reconnect. We will release it - // if we eventually connect via something else. - mWifiLock.acquire(); - - mMyWakeLock.reset(); // in case there's a leak } + updateWakeLocks(); } catch (SipException e) { Log.e(TAG, "onConnectivityChanged()", e); } -- cgit v1.2.3 From faae32cc919d4418612e6f9200d2682c0dc6d36a Mon Sep 17 00:00:00 2001 From: Steve Block Date: Tue, 20 Dec 2011 16:23:08 +0000 Subject: Rename (IF_)LOGD(_IF) to (IF_)ALOGD(_IF) DO NOT MERGE See https://android-git.corp.google.com/g/156016 Bug: 5449033 Change-Id: I4c4e33bb9df3e39e11cd985e193e6fbab4635298 --- jni/rtp/AudioGroup.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 6f8a232..270b494 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -159,7 +159,7 @@ AudioStream::~AudioStream() close(mSocket); delete mCodec; delete [] mBuffer; - LOGD("stream[%d] is dead", mSocket); + ALOGD("stream[%d] is dead", mSocket); } bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, @@ -218,7 +218,7 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, } } - LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, + ALOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode); return true; } @@ -566,7 +566,7 @@ AudioGroup::~AudioGroup() delete mChain; mChain = next; } - LOGD("group[%d] is dead", mDeviceSocket); + ALOGD("group[%d] is dead", mDeviceSocket); } bool AudioGroup::set(int sampleRate, int sampleCount) @@ -616,7 +616,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) } // Anything else? - LOGD("stream[%d] joins group[%d]", pair[1], pair[0]); + ALOGD("stream[%d] joins group[%d]", pair[1], pair[0]); return true; } @@ -639,7 +639,7 @@ bool AudioGroup::setMode(int mode) } mDeviceThread->requestExitAndWait(); - LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); + ALOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); mMode = mode; return (mode == ON_HOLD) || mDeviceThread->start(); } @@ -687,7 +687,7 @@ bool AudioGroup::add(AudioStream *stream) return false; } - LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); + ALOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); return true; } @@ -703,7 +703,7 @@ bool AudioGroup::remove(int socket) return false; } stream->mNext = target->mNext; - LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); + ALOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); delete target; break; } @@ -795,7 +795,7 @@ bool AudioGroup::DeviceThread::threadLoop() LOGE("cannot compute frame count"); return false; } - LOGD("reported frame count: output %d, input %d", output, input); + ALOGD("reported frame count: output %d, input %d", output, input); if (output < sampleCount * 2) { output = sampleCount * 2; @@ -803,7 +803,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (input < sampleCount * 2) { input = sampleCount * 2; } - LOGD("adjusted frame count: output %d, input %d", output, input); + ALOGD("adjusted frame count: output %d, input %d", output, input); // Initialize AudioTrack and AudioRecord. AudioTrack track; @@ -815,7 +815,7 @@ bool AudioGroup::DeviceThread::threadLoop() LOGE("cannot initialize audio device"); return false; } - LOGD("latency: output %d, input %d", track.latency(), record.latency()); + ALOGD("latency: output %d, input %d", track.latency(), record.latency()); // Give device socket a reasonable buffer size. setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); -- cgit v1.2.3 From 13e2c821a765845e3df0d64b9b92748c31310927 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Wed, 4 Jan 2012 20:05:49 +0000 Subject: Rename (IF_)LOGI(_IF) to (IF_)ALOGI(_IF) DO NOT MERGE See https://android-git.corp.google.com/g/156801 Bug: 5449033 Change-Id: Ib08fe86d23db91ee153e9f91a99a35c42b9208ea --- jni/rtp/EchoSuppressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index 6127d3c..e223136 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -177,7 +177,7 @@ void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) } } } - //LOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, + //ALOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, // latency * mScale); // Do echo suppression. -- cgit v1.2.3 From f2b555a7c96aea30bf5ef4076ee41d8572be6fd2 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Thu, 5 Jan 2012 23:22:43 +0000 Subject: Rename (IF_)LOGW(_IF) to (IF_)ALOGW(_IF) DO NOT MERGE See https://android-git.corp.google.com/g/157065 Bug: 5449033 Change-Id: I00a4b904f9449e6f93b7fd35eac28640d7929e69 --- jni/rtp/AudioGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 270b494..8f968ff 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -907,7 +907,7 @@ bool AudioGroup::DeviceThread::threadLoop() } if (chances <= 0) { - LOGW("device loop timeout"); + ALOGW("device loop timeout"); while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); } -- cgit v1.2.3 From 8913af783fc2cf197ef55449792bb8416a356263 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Fri, 6 Jan 2012 19:20:56 +0000 Subject: Rename (IF_)LOGE(_IF) to (IF_)ALOGE(_IF) DO NOT MERGE See https://android-git.corp.google.com/g/#/c/157220 Bug: 5449033 Change-Id: Ic9c19d30693bd56755f55906127cd6bd7126096c --- jni/rtp/AudioGroup.cpp | 32 ++++++++++++++++---------------- jni/rtp/RtpStream.cpp | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 8f968ff..4db5738 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -510,7 +510,7 @@ private: bool start() { if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - LOGE("cannot start network thread"); + ALOGE("cannot start network thread"); return false; } return true; @@ -530,7 +530,7 @@ private: bool start() { if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - LOGE("cannot start device thread"); + ALOGE("cannot start device thread"); return false; } return true; @@ -573,7 +573,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) { mEventQueue = epoll_create(2); if (mEventQueue == -1) { - LOGE("epoll_create: %s", strerror(errno)); + ALOGE("epoll_create: %s", strerror(errno)); return false; } @@ -583,7 +583,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) // Create device socket. int pair[2]; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { - LOGE("socketpair: %s", strerror(errno)); + ALOGE("socketpair: %s", strerror(errno)); return false; } mDeviceSocket = pair[0]; @@ -593,7 +593,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL, sampleRate, sampleCount, -1, -1)) { close(pair[1]); - LOGE("cannot initialize device stream"); + ALOGE("cannot initialize device stream"); return false; } @@ -602,7 +602,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) tv.tv_sec = 0; tv.tv_usec = 1000 * sampleCount / sampleRate * 500; if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { - LOGE("setsockopt: %s", strerror(errno)); + ALOGE("setsockopt: %s", strerror(errno)); return false; } @@ -611,7 +611,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) event.events = EPOLLIN; event.data.ptr = mChain; if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } @@ -675,7 +675,7 @@ bool AudioGroup::add(AudioStream *stream) event.events = EPOLLIN; event.data.ptr = stream; if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } @@ -699,7 +699,7 @@ bool AudioGroup::remove(int socket) AudioStream *target = stream->mNext; if (target->mSocket == socket) { if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } stream->mNext = target->mNext; @@ -749,7 +749,7 @@ bool AudioGroup::NetworkThread::threadLoop() epoll_event events[count]; count = epoll_wait(mGroup->mEventQueue, events, count, deadline); if (count == -1) { - LOGE("epoll_wait: %s", strerror(errno)); + ALOGE("epoll_wait: %s", strerror(errno)); return false; } for (int i = 0; i < count; ++i) { @@ -792,7 +792,7 @@ bool AudioGroup::DeviceThread::threadLoop() sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) { - LOGE("cannot compute frame count"); + ALOGE("cannot compute frame count"); return false; } ALOGD("reported frame count: output %d, input %d", output, input); @@ -812,7 +812,7 @@ bool AudioGroup::DeviceThread::threadLoop() AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) { - LOGE("cannot initialize audio device"); + ALOGE("cannot initialize audio device"); return false; } ALOGD("latency: output %d, input %d", track.latency(), record.latency()); @@ -884,7 +884,7 @@ bool AudioGroup::DeviceThread::threadLoop() toWrite -= buffer.frameCount; track.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot write to AudioTrack"); + ALOGE("cannot write to AudioTrack"); goto exit; } } @@ -900,7 +900,7 @@ bool AudioGroup::DeviceThread::threadLoop() toRead -= buffer.frameCount; record.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot read from AudioRecord"); + ALOGE("cannot read from AudioRecord"); goto exit; } } @@ -1051,7 +1051,7 @@ int registerAudioGroup(JNIEnv *env) { gRandom = open("/dev/urandom", O_RDONLY); if (gRandom == -1) { - LOGE("urandom: %s", strerror(errno)); + ALOGE("urandom: %s", strerror(errno)); return -1; } @@ -1060,7 +1060,7 @@ int registerAudioGroup(JNIEnv *env) (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { - LOGE("JNI registration failed"); + ALOGE("JNI registration failed"); return -1; } return 0; diff --git a/jni/rtp/RtpStream.cpp b/jni/rtp/RtpStream.cpp index f5efc17..6540099 100644 --- a/jni/rtp/RtpStream.cpp +++ b/jni/rtp/RtpStream.cpp @@ -116,7 +116,7 @@ int registerRtpStream(JNIEnv *env) if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { - LOGE("JNI registration failed"); + ALOGE("JNI registration failed"); return -1; } return 0; -- cgit v1.2.3 From 9a315d9c3a1291860c892d46ccd26bf466db73c4 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Thu, 12 Jan 2012 09:44:38 -0800 Subject: Fix build warnings Change-Id: I543e730aff2d03c18c26b116c9fe9419259808af --- jni/rtp/AudioGroup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 4db5738..1139577 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -1008,7 +1008,7 @@ error: delete stream; delete codec; close(socket); - env->SetIntField(thiz, gNative, NULL); + env->SetIntField(thiz, gNative, 0); } void remove(JNIEnv *env, jobject thiz, jint socket) @@ -1017,7 +1017,7 @@ void remove(JNIEnv *env, jobject thiz, jint socket) if (group) { if (socket == -1 || !group->remove(socket)) { delete group; - env->SetIntField(thiz, gNative, NULL); + env->SetIntField(thiz, gNative, 0); } } } -- cgit v1.2.3 From 4c9d58091fb7847dbf92329f6118070f42866862 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Thu, 20 Oct 2011 11:56:00 +0100 Subject: Rename (IF_)LOGV(_IF) to (IF_)ALOGV(_IF) Change-Id: I5321ebd12e9c6248a108529e82c4e1af2a4405e3 --- jni/rtp/AudioGroup.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 459756d..6f8a232 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -269,7 +269,7 @@ void AudioStream::encode(int tick, AudioStream *chain) mTick += skipped * mInterval; mSequence += skipped; mTimestamp += skipped * mSampleCount; - LOGV("stream[%d] skips %d packets", mSocket, skipped); + ALOGV("stream[%d] skips %d packets", mSocket, skipped); } tick = mTick; @@ -334,7 +334,7 @@ void AudioStream::encode(int tick, AudioStream *chain) memset(samples, 0, sizeof(samples)); if (mMode != RECEIVE_ONLY) { - LOGV("stream[%d] no data", mSocket); + ALOGV("stream[%d] no data", mSocket); } } @@ -350,7 +350,7 @@ void AudioStream::encode(int tick, AudioStream *chain) buffer[2] = mSsrc; int length = mCodec->encode(&buffer[3], samples); if (length <= 0) { - LOGV("stream[%d] encoder error", mSocket); + ALOGV("stream[%d] encoder error", mSocket); return; } sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, @@ -386,7 +386,7 @@ void AudioStream::decode(int tick) mLatencyScore = score; mLatencyTimer = tick; } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { - LOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); + ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); mBufferTail -= mLatencyScore; mLatencyScore = -1; } @@ -394,7 +394,7 @@ void AudioStream::decode(int tick) int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; if (count < mSampleCount) { // Buffer overflow. Drop the packet. - LOGV("stream[%d] buffer overflow", mSocket); + ALOGV("stream[%d] buffer overflow", mSocket); recv(mSocket, &c, 1, MSG_DONTWAIT); return; } @@ -417,7 +417,7 @@ void AudioStream::decode(int tick) // reliable but at least they can be used to identify duplicates? if (length < 12 || length > (int)sizeof(buffer) || (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { - LOGV("stream[%d] malformed packet", mSocket); + ALOGV("stream[%d] malformed packet", mSocket); return; } int offset = 12 + ((buffer[0] & 0x0F) << 2); @@ -438,13 +438,13 @@ void AudioStream::decode(int tick) count = length; } if (count <= 0) { - LOGV("stream[%d] decoder error", mSocket); + ALOGV("stream[%d] decoder error", mSocket); return; } if (tick - mBufferTail > 0) { // Buffer underrun. Reset the jitter buffer. - LOGV("stream[%d] buffer underrun", mSocket); + ALOGV("stream[%d] buffer underrun", mSocket); if (mBufferTail - mBufferHead <= 0) { mBufferHead = tick + mInterval; mBufferTail = mBufferHead; @@ -913,7 +913,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (mode != MUTED) { if (echo != NULL) { - LOGV("echo->run()"); + ALOGV("echo->run()"); echo->run(output, input); } send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); -- cgit v1.2.3 From 655ddfe4c39cba018265746e9ae575d76a7de135 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Tue, 20 Dec 2011 16:23:08 +0000 Subject: Rename (IF_)LOGD(_IF) to (IF_)ALOGD(_IF) Change-Id: I44f267700356967dc51e8f85ebf457dc85cfb229 --- jni/rtp/AudioGroup.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 6f8a232..270b494 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -159,7 +159,7 @@ AudioStream::~AudioStream() close(mSocket); delete mCodec; delete [] mBuffer; - LOGD("stream[%d] is dead", mSocket); + ALOGD("stream[%d] is dead", mSocket); } bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, @@ -218,7 +218,7 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, } } - LOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, + ALOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode); return true; } @@ -566,7 +566,7 @@ AudioGroup::~AudioGroup() delete mChain; mChain = next; } - LOGD("group[%d] is dead", mDeviceSocket); + ALOGD("group[%d] is dead", mDeviceSocket); } bool AudioGroup::set(int sampleRate, int sampleCount) @@ -616,7 +616,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) } // Anything else? - LOGD("stream[%d] joins group[%d]", pair[1], pair[0]); + ALOGD("stream[%d] joins group[%d]", pair[1], pair[0]); return true; } @@ -639,7 +639,7 @@ bool AudioGroup::setMode(int mode) } mDeviceThread->requestExitAndWait(); - LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); + ALOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); mMode = mode; return (mode == ON_HOLD) || mDeviceThread->start(); } @@ -687,7 +687,7 @@ bool AudioGroup::add(AudioStream *stream) return false; } - LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); + ALOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); return true; } @@ -703,7 +703,7 @@ bool AudioGroup::remove(int socket) return false; } stream->mNext = target->mNext; - LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); + ALOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); delete target; break; } @@ -795,7 +795,7 @@ bool AudioGroup::DeviceThread::threadLoop() LOGE("cannot compute frame count"); return false; } - LOGD("reported frame count: output %d, input %d", output, input); + ALOGD("reported frame count: output %d, input %d", output, input); if (output < sampleCount * 2) { output = sampleCount * 2; @@ -803,7 +803,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (input < sampleCount * 2) { input = sampleCount * 2; } - LOGD("adjusted frame count: output %d, input %d", output, input); + ALOGD("adjusted frame count: output %d, input %d", output, input); // Initialize AudioTrack and AudioRecord. AudioTrack track; @@ -815,7 +815,7 @@ bool AudioGroup::DeviceThread::threadLoop() LOGE("cannot initialize audio device"); return false; } - LOGD("latency: output %d, input %d", track.latency(), record.latency()); + ALOGD("latency: output %d, input %d", track.latency(), record.latency()); // Give device socket a reasonable buffer size. setsockopt(deviceSocket, SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)); -- cgit v1.2.3 From 1931f2c107d444a935a276fa2d4545bef9b74e27 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Wed, 4 Jan 2012 20:05:49 +0000 Subject: Rename (IF_)LOGI(_IF) to (IF_)ALOGI(_IF) Change-Id: I26f76452ac49e2890b14d133c065493d8df0fb4a --- jni/rtp/EchoSuppressor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/EchoSuppressor.cpp b/jni/rtp/EchoSuppressor.cpp index 6127d3c..e223136 100644 --- a/jni/rtp/EchoSuppressor.cpp +++ b/jni/rtp/EchoSuppressor.cpp @@ -177,7 +177,7 @@ void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) } } } - //LOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, + //ALOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, // latency * mScale); // Do echo suppression. -- cgit v1.2.3 From 1cec3b9822c57bb1574b3e4ca2e4f70172ec7089 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Thu, 5 Jan 2012 23:22:43 +0000 Subject: Rename (IF_)LOGW(_IF) to (IF_)ALOGW(_IF) Change-Id: I8fbdfa7a7581f481968dbb65aa40f7042936d7cb --- jni/rtp/AudioGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 270b494..8f968ff 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -907,7 +907,7 @@ bool AudioGroup::DeviceThread::threadLoop() } if (chances <= 0) { - LOGW("device loop timeout"); + ALOGW("device loop timeout"); while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); } -- cgit v1.2.3 From 27e97c62dfa8e6b9ea478f50fc6794a65a1ea0ca Mon Sep 17 00:00:00 2001 From: Steve Block Date: Fri, 6 Jan 2012 19:20:56 +0000 Subject: Rename (IF_)LOGE(_IF) to (IF_)ALOGE(_IF) Change-Id: I1de629b4632a4b3187ca1a28d6416daccd35f924 --- jni/rtp/AudioGroup.cpp | 32 ++++++++++++++++---------------- jni/rtp/RtpStream.cpp | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 8f968ff..4db5738 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -510,7 +510,7 @@ private: bool start() { if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - LOGE("cannot start network thread"); + ALOGE("cannot start network thread"); return false; } return true; @@ -530,7 +530,7 @@ private: bool start() { if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - LOGE("cannot start device thread"); + ALOGE("cannot start device thread"); return false; } return true; @@ -573,7 +573,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) { mEventQueue = epoll_create(2); if (mEventQueue == -1) { - LOGE("epoll_create: %s", strerror(errno)); + ALOGE("epoll_create: %s", strerror(errno)); return false; } @@ -583,7 +583,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) // Create device socket. int pair[2]; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { - LOGE("socketpair: %s", strerror(errno)); + ALOGE("socketpair: %s", strerror(errno)); return false; } mDeviceSocket = pair[0]; @@ -593,7 +593,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL, sampleRate, sampleCount, -1, -1)) { close(pair[1]); - LOGE("cannot initialize device stream"); + ALOGE("cannot initialize device stream"); return false; } @@ -602,7 +602,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) tv.tv_sec = 0; tv.tv_usec = 1000 * sampleCount / sampleRate * 500; if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { - LOGE("setsockopt: %s", strerror(errno)); + ALOGE("setsockopt: %s", strerror(errno)); return false; } @@ -611,7 +611,7 @@ bool AudioGroup::set(int sampleRate, int sampleCount) event.events = EPOLLIN; event.data.ptr = mChain; if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } @@ -675,7 +675,7 @@ bool AudioGroup::add(AudioStream *stream) event.events = EPOLLIN; event.data.ptr = stream; if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } @@ -699,7 +699,7 @@ bool AudioGroup::remove(int socket) AudioStream *target = stream->mNext; if (target->mSocket == socket) { if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) { - LOGE("epoll_ctl: %s", strerror(errno)); + ALOGE("epoll_ctl: %s", strerror(errno)); return false; } stream->mNext = target->mNext; @@ -749,7 +749,7 @@ bool AudioGroup::NetworkThread::threadLoop() epoll_event events[count]; count = epoll_wait(mGroup->mEventQueue, events, count, deadline); if (count == -1) { - LOGE("epoll_wait: %s", strerror(errno)); + ALOGE("epoll_wait: %s", strerror(errno)); return false; } for (int i = 0; i < count; ++i) { @@ -792,7 +792,7 @@ bool AudioGroup::DeviceThread::threadLoop() sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) { - LOGE("cannot compute frame count"); + ALOGE("cannot compute frame count"); return false; } ALOGD("reported frame count: output %d, input %d", output, input); @@ -812,7 +812,7 @@ bool AudioGroup::DeviceThread::threadLoop() AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) { - LOGE("cannot initialize audio device"); + ALOGE("cannot initialize audio device"); return false; } ALOGD("latency: output %d, input %d", track.latency(), record.latency()); @@ -884,7 +884,7 @@ bool AudioGroup::DeviceThread::threadLoop() toWrite -= buffer.frameCount; track.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot write to AudioTrack"); + ALOGE("cannot write to AudioTrack"); goto exit; } } @@ -900,7 +900,7 @@ bool AudioGroup::DeviceThread::threadLoop() toRead -= buffer.frameCount; record.releaseBuffer(&buffer); } else if (status != TIMED_OUT && status != WOULD_BLOCK) { - LOGE("cannot read from AudioRecord"); + ALOGE("cannot read from AudioRecord"); goto exit; } } @@ -1051,7 +1051,7 @@ int registerAudioGroup(JNIEnv *env) { gRandom = open("/dev/urandom", O_RDONLY); if (gRandom == -1) { - LOGE("urandom: %s", strerror(errno)); + ALOGE("urandom: %s", strerror(errno)); return -1; } @@ -1060,7 +1060,7 @@ int registerAudioGroup(JNIEnv *env) (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { - LOGE("JNI registration failed"); + ALOGE("JNI registration failed"); return -1; } return 0; diff --git a/jni/rtp/RtpStream.cpp b/jni/rtp/RtpStream.cpp index f5efc17..6540099 100644 --- a/jni/rtp/RtpStream.cpp +++ b/jni/rtp/RtpStream.cpp @@ -116,7 +116,7 @@ int registerRtpStream(JNIEnv *env) if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { - LOGE("JNI registration failed"); + ALOGE("JNI registration failed"); return -1; } return 0; -- cgit v1.2.3 From f4d203203fd27aa0e3cfdafe3cea6c719f04e881 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Tue, 13 Mar 2012 15:59:35 -0700 Subject: Remove dependency on audio_* location Change-Id: I4bc66115fcb9ba22b057bd72db3f561dcb18a0d8 --- jni/rtp/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 0815294..49b711d 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -50,7 +50,7 @@ LOCAL_C_INCLUDES += \ frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ frameworks/base/media/libstagefright/codecs/amrnb/dec/include \ frameworks/base/media/libstagefright/codecs/amrnb/dec/src \ - system/media/audio_effects/include + $(call include-path-for, audio-effects) LOCAL_CFLAGS += -fvisibility=hidden -- cgit v1.2.3 From 49952644906debfafa1b830a2c532103e7afbcbc Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Fri, 16 Mar 2012 11:42:24 -0700 Subject: Add libmedia_native Change-Id: Ib8cff8abd73723b793f08da99ad59549f219e0e7 --- jni/rtp/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 49b711d..d725713 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -37,6 +37,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ + libmedia_native \ libstagefright_amrnb_common LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc -- cgit v1.2.3 From 49ba22c1e66a34dfb9266613e2bc836f6a9dc5cd Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Wed, 14 Mar 2012 12:56:26 -0700 Subject: Whitespace Fix indentation, and add blank lines in key places for clarity Change-Id: I57a0a8142394f83203161aa9b8aa9276abf3ed7c --- jni/rtp/AudioGroup.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 1139577..b9bbd16 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -809,9 +809,9 @@ bool AudioGroup::DeviceThread::threadLoop() AudioTrack track; AudioRecord record; if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT, - AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set( - AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT, - AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) { + AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || + record.set(AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT, + AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) { ALOGE("cannot initialize audio device"); return false; } -- cgit v1.2.3 From d6024345f461f757df3d493c622e68b07199a2c0 Mon Sep 17 00:00:00 2001 From: The Android Automerger Date: Tue, 20 Mar 2012 14:14:30 -0700 Subject: merge in jb-release history after reset to master --- jni/rtp/Android.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index d725713..49b711d 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -37,7 +37,6 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ - libmedia_native \ libstagefright_amrnb_common LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc -- cgit v1.2.3 From 66c85d703e3e5a68febeba966a6a125d302b7226 Mon Sep 17 00:00:00 2001 From: The Android Automerger Date: Wed, 21 Mar 2012 07:03:25 -0700 Subject: merge in jb-release history after reset to master --- jni/rtp/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 49b711d..d725713 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -37,6 +37,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ + libmedia_native \ libstagefright_amrnb_common LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc -- cgit v1.2.3 From c30207630321a215ae87e34f4ac4516274755959 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 26 Mar 2012 16:28:42 -0700 Subject: move hardware feature definitions Move the hardware feature xml files from frameworks/base/data/etc to frameworks/native/data/etc. Change-Id: If7dc9d68c0c57516adb8e863b68c8252abd6014c --- data/etc/android.software.sip.voip.xml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 data/etc/android.software.sip.voip.xml diff --git a/data/etc/android.software.sip.voip.xml b/data/etc/android.software.sip.voip.xml deleted file mode 100644 index edd06c1..0000000 --- a/data/etc/android.software.sip.voip.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - -- cgit v1.2.3 From a7857fbfdfccacb95306e32511f57f1a017b0e31 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Tue, 27 Mar 2012 15:27:25 -0700 Subject: RTP: add a null-check in AudioStream.setDtmfType(). Change-Id: I52cbdea48affae3747942940451f4fd5ca47030f --- java/android/net/rtp/AudioStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index d761214..b7874f7 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -158,7 +158,7 @@ public class AudioStream extends RtpStream { if (type < 96 || type > 127) { throw new IllegalArgumentException("Invalid type"); } - if (type == mCodec.type) { + if (mCodec != null && type == mCodec.type) { throw new IllegalArgumentException("The type is used by codec"); } } -- cgit v1.2.3 From 659454aab43e59005e6fe1ccd22b72df8913176e Mon Sep 17 00:00:00 2001 From: James Dong Date: Wed, 28 Mar 2012 11:03:25 -0700 Subject: frameworks base Android.mk file changes Change-Id: I7459b9e959a60751b8fa6e0d893cb2c820c064ce --- jni/rtp/Android.mk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index d725713..82d7912 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -45,12 +45,12 @@ LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ external/libgsm/inc \ - frameworks/base/media/libstagefright/codecs/amrnb/common/include \ - frameworks/base/media/libstagefright/codecs/amrnb/common/ \ - frameworks/base/media/libstagefright/codecs/amrnb/enc/include \ - frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ - frameworks/base/media/libstagefright/codecs/amrnb/dec/include \ - frameworks/base/media/libstagefright/codecs/amrnb/dec/src \ + frameworks/av/media/libstagefright/codecs/amrnb/common/include \ + frameworks/av/media/libstagefright/codecs/amrnb/common/ \ + frameworks/av/media/libstagefright/codecs/amrnb/enc/include \ + frameworks/av/media/libstagefright/codecs/amrnb/enc/src \ + frameworks/av/media/libstagefright/codecs/amrnb/dec/include \ + frameworks/av/media/libstagefright/codecs/amrnb/dec/src \ $(call include-path-for, audio-effects) LOCAL_CFLAGS += -fvisibility=hidden -- cgit v1.2.3 From 2bf2e642d061e7b48dd71927752e9151a5126fb2 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Fri, 30 Mar 2012 13:25:19 -0700 Subject: RTP: refactor a little bit and fix few minor bugs. Change-Id: I063644507f26996ded462972afcb550a4528dac8 --- java/android/net/rtp/AudioGroup.java | 27 ++++++++++++----------- java/android/net/rtp/AudioStream.java | 2 +- java/android/net/rtp/RtpStream.java | 16 ++++++++------ jni/rtp/AudioGroup.cpp | 40 ++++++++++++++++++++--------------- jni/rtp/RtpStream.cpp | 24 ++++++--------------- 5 files changed, 54 insertions(+), 55 deletions(-) diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index 3e7ace8..8c19062 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -142,34 +142,34 @@ public class AudioGroup { private native void nativeSetMode(int mode); // Package-private method used by AudioStream.join(). - synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { + synchronized void add(AudioStream stream) { if (!mStreams.containsKey(stream)) { try { - int socket = stream.dup(); + AudioCodec codec = stream.getCodec(); String codecSpec = String.format("%d %s %s", codec.type, codec.rtpmap, codec.fmtp); - nativeAdd(stream.getMode(), socket, + int id = nativeAdd(stream.getMode(), stream.getSocket(), stream.getRemoteAddress().getHostAddress(), - stream.getRemotePort(), codecSpec, dtmfType); - mStreams.put(stream, socket); + stream.getRemotePort(), codecSpec, stream.getDtmfType()); + mStreams.put(stream, id); } catch (NullPointerException e) { throw new IllegalStateException(e); } } } - private native void nativeAdd(int mode, int socket, String remoteAddress, + private native int nativeAdd(int mode, int socket, String remoteAddress, int remotePort, String codecSpec, int dtmfType); // Package-private method used by AudioStream.join(). synchronized void remove(AudioStream stream) { - Integer socket = mStreams.remove(stream); - if (socket != null) { - nativeRemove(socket); + Integer id = mStreams.remove(stream); + if (id != null) { + nativeRemove(id); } } - private native void nativeRemove(int socket); + private native void nativeRemove(int id); /** * Sends a DTMF digit to every {@link AudioStream} in this group. Currently @@ -192,15 +192,14 @@ public class AudioGroup { * Removes every {@link AudioStream} in this group. */ public void clear() { - synchronized (this) { - mStreams.clear(); - nativeRemove(-1); + for (AudioStream stream : getStreams()) { + stream.join(null); } } @Override protected void finalize() throws Throwable { - clear(); + nativeRemove(0); super.finalize(); } } diff --git a/java/android/net/rtp/AudioStream.java b/java/android/net/rtp/AudioStream.java index b7874f7..5cd1abc 100644 --- a/java/android/net/rtp/AudioStream.java +++ b/java/android/net/rtp/AudioStream.java @@ -94,7 +94,7 @@ public class AudioStream extends RtpStream { mGroup = null; } if (group != null) { - group.add(this, mCodec, mDtmfType); + group.add(this); mGroup = group; } } diff --git a/java/android/net/rtp/RtpStream.java b/java/android/net/rtp/RtpStream.java index e94ac42..b9d75cd 100644 --- a/java/android/net/rtp/RtpStream.java +++ b/java/android/net/rtp/RtpStream.java @@ -54,7 +54,7 @@ public class RtpStream { private int mRemotePort = -1; private int mMode = MODE_NORMAL; - private int mNative; + private int mSocket = -1; static { System.loadLibrary("rtp_jni"); } @@ -165,7 +165,9 @@ public class RtpStream { mRemotePort = port; } - synchronized native int dup(); + int getSocket() { + return mSocket; + } /** * Releases allocated resources. The stream becomes inoperable after calling @@ -175,13 +177,15 @@ public class RtpStream { * @see #isBusy() */ public void release() { - if (isBusy()) { - throw new IllegalStateException("Busy"); + synchronized (this) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + close(); } - close(); } - private synchronized native void close(); + private native void close(); @Override protected void finalize() throws Throwable { diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index b9bbd16..673a650 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -478,7 +478,7 @@ public: bool setMode(int mode); bool sendDtmf(int event); bool add(AudioStream *stream); - bool remove(int socket); + bool remove(AudioStream *stream); bool platformHasAec() { return mPlatformHasAec; } private: @@ -691,20 +691,19 @@ bool AudioGroup::add(AudioStream *stream) return true; } -bool AudioGroup::remove(int socket) +bool AudioGroup::remove(AudioStream *stream) { mNetworkThread->requestExitAndWait(); - for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) { - AudioStream *target = stream->mNext; - if (target->mSocket == socket) { - if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) { + for (AudioStream *chain = mChain; chain->mNext; chain = chain->mNext) { + if (chain->mNext == stream) { + if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, stream->mSocket, NULL)) { ALOGE("epoll_ctl: %s", strerror(errno)); return false; } - stream->mNext = target->mNext; - ALOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); - delete target; + chain->mNext = stream->mNext; + ALOGD("stream[%d] leaves group[%d]", stream->mSocket, mDeviceSocket); + delete stream; break; } } @@ -931,7 +930,7 @@ exit: static jfieldID gNative; static jfieldID gMode; -void add(JNIEnv *env, jobject thiz, jint mode, +int add(JNIEnv *env, jobject thiz, jint mode, jint socket, jstring jRemoteAddress, jint remotePort, jstring jCodecSpec, jint dtmfType) { @@ -943,16 +942,22 @@ void add(JNIEnv *env, jobject thiz, jint mode, sockaddr_storage remote; if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { // Exception already thrown. - return; + return 0; } if (!jCodecSpec) { jniThrowNullPointerException(env, "codecSpec"); - return; + return 0; } const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL); if (!codecSpec) { // Exception already thrown. - return; + return 0; + } + socket = dup(socket); + if (socket == -1) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get stream socket"); + return 0; } // Create audio codec. @@ -1001,7 +1006,7 @@ void add(JNIEnv *env, jobject thiz, jint mode, // Succeed. env->SetIntField(thiz, gNative, (int)group); - return; + return (int)stream; error: delete group; @@ -1009,13 +1014,14 @@ error: delete codec; close(socket); env->SetIntField(thiz, gNative, 0); + return 0; } -void remove(JNIEnv *env, jobject thiz, jint socket) +void remove(JNIEnv *env, jobject thiz, jint stream) { AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); if (group) { - if (socket == -1 || !group->remove(socket)) { + if (!stream || !group->remove((AudioStream *)stream)) { delete group; env->SetIntField(thiz, gNative, 0); } @@ -1039,7 +1045,7 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event) } JNINativeMethod gMethods[] = { - {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add}, + {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)I", (void *)add}, {"nativeRemove", "(I)V", (void *)remove}, {"nativeSetMode", "(I)V", (void *)setMode}, {"nativeSendDtmf", "(I)V", (void *)sendDtmf}, diff --git a/jni/rtp/RtpStream.cpp b/jni/rtp/RtpStream.cpp index 6540099..bfe8e24 100644 --- a/jni/rtp/RtpStream.cpp +++ b/jni/rtp/RtpStream.cpp @@ -33,11 +33,11 @@ extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); namespace { -jfieldID gNative; +jfieldID gSocket; jint create(JNIEnv *env, jobject thiz, jstring jAddress) { - env->SetIntField(thiz, gNative, -1); + env->SetIntField(thiz, gSocket, -1); sockaddr_storage ss; if (parse(env, jAddress, 0, &ss) < 0) { @@ -58,7 +58,7 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port; uint16_t port = ntohs(*p); if ((port & 1) == 0) { - env->SetIntField(thiz, gNative, socket); + env->SetIntField(thiz, gSocket, socket); return port; } ::close(socket); @@ -75,7 +75,7 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) *p = htons(port); if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) { - env->SetIntField(thiz, gNative, socket); + env->SetIntField(thiz, gSocket, socket); return port; } } @@ -86,25 +86,15 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) return -1; } -jint dup(JNIEnv *env, jobject thiz) -{ - int socket = ::dup(env->GetIntField(thiz, gNative)); - if (socket == -1) { - jniThrowException(env, "java/lang/IllegalStateException", strerror(errno)); - } - return socket; -} - void close(JNIEnv *env, jobject thiz) { - int socket = env->GetIntField(thiz, gNative); + int socket = env->GetIntField(thiz, gSocket); ::close(socket); - env->SetIntField(thiz, gNative, -1); + env->SetIntField(thiz, gSocket, -1); } JNINativeMethod gMethods[] = { {"create", "(Ljava/lang/String;)I", (void *)create}, - {"dup", "()I", (void *)dup}, {"close", "()V", (void *)close}, }; @@ -114,7 +104,7 @@ int registerRtpStream(JNIEnv *env) { jclass clazz; if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || - (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + (gSocket = env->GetFieldID(clazz, "mSocket", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { ALOGE("JNI registration failed"); return -1; -- cgit v1.2.3 From 1b5111f16c1401437d577dfd547af10aa691d44f Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Wed, 4 Apr 2012 13:04:26 -0700 Subject: SIP: push the logic of finding local address down to SipSessionGroup. This allows different accounts binding on different IP addresses, such as one on IPv4 and another on IPv6. Bug: 4169057 Change-Id: I0bb36669394c281330091673fa338adea8f782cd --- java/com/android/server/sip/SipService.java | 44 +--------- java/com/android/server/sip/SipSessionGroup.java | 105 ++++++++++++----------- 2 files changed, 60 insertions(+), 89 deletions(-) diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index 97afc81..a477fd1 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -453,9 +453,8 @@ public final class SipService extends ISipService.Stub { public SipSessionGroupExt(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener) throws SipException { - String password = localProfile.getPassword(); - SipProfile p = duplicate(localProfile); - mSipGroup = createSipSessionGroup(mLocalIp, p, password); + mSipGroup = new SipSessionGroup(duplicate(localProfile), + localProfile.getPassword(), mTimer, mMyWakeLock); mIncomingCallPendingIntent = incomingCallPendingIntent; mAutoRegistration.setListener(listener); } @@ -478,27 +477,6 @@ public final class SipService extends ISipService.Stub { mSipGroup.setWakeupTimer(timer); } - // network connectivity is tricky because network can be disconnected - // at any instant so need to deal with exceptions carefully even when - // you think you are connected - private SipSessionGroup createSipSessionGroup(String localIp, - SipProfile localProfile, String password) throws SipException { - try { - return new SipSessionGroup(localIp, localProfile, password, - mTimer, mMyWakeLock); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "createSipSessionGroup(): network disconnected?"); - if (localIp != null) { - return createSipSessionGroup(null, localProfile, password); - } else { - // recursive - Log.wtf(TAG, "impossible! recursive!"); - throw new RuntimeException("createSipSessionGroup"); - } - } - } - private SipProfile duplicate(SipProfile p) { try { return new SipProfile.Builder(p).setPassword("*").build(); @@ -530,7 +508,7 @@ public final class SipService extends ISipService.Stub { throws SipException { mSipGroup.onConnectivityChanged(); if (connected) { - resetGroup(mLocalIp); + mSipGroup.reset(); if (mOpenedToReceiveCalls) openToReceiveCalls(); } else { // close mSipGroup but remember mOpenedToReceiveCalls @@ -541,22 +519,6 @@ public final class SipService extends ISipService.Stub { } } - private void resetGroup(String localIp) throws SipException { - try { - mSipGroup.reset(localIp); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "resetGroup(): network disconnected?"); - if (localIp != null) { - resetGroup(null); // reset w/o local IP - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("resetGroup"); - } - } - } - public void close() { mOpenedToReceiveCalls = false; mSipGroup.close(); diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 877a0a4..6acd456 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -40,6 +40,7 @@ import android.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.UnknownHostException; import java.text.ParseException; import java.util.Collection; @@ -47,13 +48,11 @@ import java.util.EventObject; import java.util.HashMap; import java.util.Map; import java.util.Properties; -import java.util.TooManyListenersException; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.RequestEvent; @@ -132,18 +131,17 @@ class SipSessionGroup implements SipListener { private int mExternalPort; /** - * @param myself the local profile with password crossed out + * @param profile the local profile with password crossed out * @param password the password of the profile * @throws IOException if cannot assign requested address */ - public SipSessionGroup(String localIp, SipProfile myself, String password, - SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, - IOException { - mLocalProfile = myself; + public SipSessionGroup(SipProfile profile, String password, + SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException { + mLocalProfile = profile; mPassword = password; mWakeupTimer = timer; mWakeLock = wakeLock; - reset(localIp); + reset(); } // TODO: remove this method once SipWakeupTimer can better handle variety @@ -152,43 +150,64 @@ class SipSessionGroup implements SipListener { mWakeupTimer = timer; } - synchronized void reset(String localIp) throws SipException, IOException { - mLocalIp = localIp; - if (localIp == null) return; - - SipProfile myself = mLocalProfile; - SipFactory sipFactory = SipFactory.getInstance(); + synchronized void reset() throws SipException { Properties properties = new Properties(); + + String protocol = mLocalProfile.getProtocol(); + int port = mLocalProfile.getPort(); + String server = mLocalProfile.getProxyAddress(); + + if (!TextUtils.isEmpty(server)) { + properties.setProperty("javax.sip.OUTBOUND_PROXY", + server + ':' + port + '/' + protocol); + } else { + server = mLocalProfile.getSipDomain(); + } + if (server.startsWith("[") && server.endsWith("]")) { + server = server.substring(1, server.length() - 1); + } + + String local = null; + try { + for (InetAddress remote : InetAddress.getAllByName(server)) { + DatagramSocket socket = new DatagramSocket(); + socket.connect(remote, port); + if (socket.isConnected()) { + local = socket.getLocalAddress().getHostAddress(); + port = socket.getLocalPort(); + socket.close(); + break; + } + socket.close(); + } + } catch (Exception e) { + // ignore. + } + if (local == null) { + // We are unable to reach the server. Just bail out. + return; + } + + close(); + mLocalIp = local; + properties.setProperty("javax.sip.STACK_NAME", getStackName()); properties.setProperty( "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); - String outboundProxy = myself.getProxyAddress(); - if (!TextUtils.isEmpty(outboundProxy)) { - Log.v(TAG, "outboundProxy is " + outboundProxy); - properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy - + ":" + myself.getPort() + "/" + myself.getProtocol()); - } - SipStack stack = mSipStack = sipFactory.createSipStack(properties); - + mSipStack = SipFactory.getInstance().createSipStack(properties); try { - SipProvider provider = stack.createSipProvider( - stack.createListeningPoint(localIp, allocateLocalPort(), - myself.getProtocol())); + SipProvider provider = mSipStack.createSipProvider( + mSipStack.createListeningPoint(local, port, protocol)); provider.addSipListener(this); - mSipHelper = new SipHelper(stack, provider); - } catch (InvalidArgumentException e) { - throw new IOException(e.getMessage()); - } catch (TooManyListenersException e) { - // must never happen - throw new SipException("SipSessionGroup constructor", e); + mSipHelper = new SipHelper(mSipStack, provider); + } catch (SipException e) { + throw e; + } catch (Exception e) { + throw new SipException("failed to initialize SIP stack", e); } - Log.d(TAG, " start stack for " + myself.getUriString()); - stack.start(); - - mCallReceiverSession = null; - mSessionMap.clear(); - resetExternalAddress(); + Log.d(TAG, " start stack for " + mLocalProfile.getUriString()); + mSipStack.start(); } synchronized void onConnectivityChanged() { @@ -234,6 +253,7 @@ class SipSessionGroup implements SipListener { mSipStack = null; mSipHelper = null; } + resetExternalAddress(); } public synchronized boolean isClosed() { @@ -257,17 +277,6 @@ class SipSessionGroup implements SipListener { return (isClosed() ? null : new SipSessionImpl(listener)); } - private static int allocateLocalPort() throws SipException { - try { - DatagramSocket s = new DatagramSocket(); - int localPort = s.getLocalPort(); - s.close(); - return localPort; - } catch (IOException e) { - throw new SipException("allocateLocalPort()", e); - } - } - synchronized boolean containsSession(String callId) { return mSessionMap.containsKey(callId); } -- cgit v1.2.3 From 91f8f76abd63a71295819551aba71fc734e1e710 Mon Sep 17 00:00:00 2001 From: Scott Main Date: Fri, 22 Jun 2012 12:35:08 -0700 Subject: docs: fix several links Change-Id: I89d9fd64dc22c90680bb05415cc966c255165af9 --- java/android/net/sip/package.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/android/net/sip/package.html b/java/android/net/sip/package.html index eb683d0..3c4cc23 100644 --- a/java/android/net/sip/package.html +++ b/java/android/net/sip/package.html @@ -4,7 +4,7 @@ making and answering VOIP calls using SIP.

    For more information, see the -Session Initiation Protocol +Session Initiation Protocol developer guide.

    {@more} -- cgit v1.2.3 From f29ac10f0f3649c16a6e44bac961401cd3874fd4 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Mon, 2 Jul 2012 13:12:31 -0700 Subject: Use audio_channel_mask_t more consistently In AudioRecord::getMinFrameCount() and AudioSystem::getInputBufferSize(), input parameter is channel mask instead of channel count. Change-Id: I22a1c492113f3e689173c5ab97b2567cff3abe2b --- jni/rtp/AudioGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 673a650..1579e6a 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -790,7 +790,7 @@ bool AudioGroup::DeviceThread::threadLoop() if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL, sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, - AUDIO_FORMAT_PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_MONO) != NO_ERROR || input <= 0) { ALOGE("cannot compute frame count"); return false; } -- cgit v1.2.3 From 0226eb00cecf82b53e29758c04cd1b39015d2909 Mon Sep 17 00:00:00 2001 From: Johan Redestig Date: Wed, 29 Aug 2012 08:19:32 +0200 Subject: Make SimpleSessionDescription locale safe Explicitly use Locale.US in SimpleSessionDescription to avoid unexpected results in some locales. Change-Id: Idb4a36a9e332d302e1b9b940355917c0f738e076 --- java/android/net/sip/SimpleSessionDescription.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/android/net/sip/SimpleSessionDescription.java b/java/android/net/sip/SimpleSessionDescription.java index 29166dc..9fcd21d 100644 --- a/java/android/net/sip/SimpleSessionDescription.java +++ b/java/android/net/sip/SimpleSessionDescription.java @@ -18,6 +18,7 @@ package android.net.sip; import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; /** * An object used to manipulate messages of Session Description Protocol (SDP). @@ -66,7 +67,7 @@ public class SimpleSessionDescription { public SimpleSessionDescription(long sessionId, String address) { address = (address.indexOf(':') < 0 ? "IN IP4 " : "IN IP6 ") + address; mFields.parse("v=0"); - mFields.parse(String.format("o=- %d %d %s", sessionId, + mFields.parse(String.format(Locale.US, "o=- %d %d %s", sessionId, System.currentTimeMillis(), address)); mFields.parse("s=-"); mFields.parse("t=0 0"); -- cgit v1.2.3 From 3fc95054ca84da3f78935774023f61587ec3beb5 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Tue, 30 Oct 2012 10:59:52 -0700 Subject: Remove obsolete references to libmedia_native Bug: 6654403 Change-Id: I05d8e81fd31617b587fd1228a303c40db83e7f2d --- jni/rtp/Android.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/jni/rtp/Android.mk b/jni/rtp/Android.mk index 82d7912..b265cdd 100644 --- a/jni/rtp/Android.mk +++ b/jni/rtp/Android.mk @@ -37,7 +37,6 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libmedia \ - libmedia_native \ libstagefright_amrnb_common LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc -- cgit v1.2.3 From 86d1860a1d9c905bf8ee8328bcee4537cd02fcd6 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Tue, 13 Nov 2012 15:21:06 -0800 Subject: Use size_t for frame count Change-Id: Idd364443715a920ece2cc54acc95b395b6ed2c6c --- jni/rtp/AudioGroup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jni/rtp/AudioGroup.cpp b/jni/rtp/AudioGroup.cpp index 1579e6a..2f0829e 100644 --- a/jni/rtp/AudioGroup.cpp +++ b/jni/rtp/AudioGroup.cpp @@ -785,8 +785,8 @@ bool AudioGroup::DeviceThread::threadLoop() int deviceSocket = mGroup->mDeviceSocket; // Find out the frame count for AudioTrack and AudioRecord. - int output = 0; - int input = 0; + size_t output = 0; + size_t input = 0; if (AudioTrack::getMinFrameCount(&output, AUDIO_STREAM_VOICE_CALL, sampleRate) != NO_ERROR || output <= 0 || AudioRecord::getMinFrameCount(&input, sampleRate, -- cgit v1.2.3 From fe2164c76a5715e0a8106aacfd78a372d510bc04 Mon Sep 17 00:00:00 2001 From: Johan Redestig Date: Tue, 28 Aug 2012 09:37:23 +0200 Subject: Make AudioGroup.add locale safe Explicitly use Locale.US in AudioGroup.add to avoid unexpected results in some locales. Change-Id: Ifb477ca590f630747e09e38ac2246d284b5c5bfc --- java/android/net/rtp/AudioGroup.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/android/net/rtp/AudioGroup.java b/java/android/net/rtp/AudioGroup.java index 8c19062..8faeb88 100644 --- a/java/android/net/rtp/AudioGroup.java +++ b/java/android/net/rtp/AudioGroup.java @@ -19,6 +19,7 @@ package android.net.rtp; import android.media.AudioManager; import java.util.HashMap; +import java.util.Locale; import java.util.Map; /** @@ -146,7 +147,7 @@ public class AudioGroup { if (!mStreams.containsKey(stream)) { try { AudioCodec codec = stream.getCodec(); - String codecSpec = String.format("%d %s %s", codec.type, + String codecSpec = String.format(Locale.US, "%d %s %s", codec.type, codec.rtpmap, codec.fmtp); int id = nativeAdd(stream.getMode(), stream.getSocket(), stream.getRemoteAddress().getHostAddress(), -- cgit v1.2.3 From 9329db04f13480ccdff013dcc00cdb96f12a921c Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Tue, 22 Jan 2013 16:01:58 -0800 Subject: Add debug and some cleanup Change-Id: I866676a1ec4a338dcfe089cbf0483e5e546ded85 --- java/android/net/sip/SipAudioCall.java | 95 ++++--- java/android/net/sip/SipManager.java | 5 +- java/android/net/sip/SipSession.java | 45 ++-- java/com/android/server/sip/SipHelper.java | 40 +-- java/com/android/server/sip/SipService.java | 271 ++++++++++++-------- java/com/android/server/sip/SipSessionGroup.java | 272 ++++++++++++--------- .../server/sip/SipSessionListenerProxy.java | 40 ++- java/com/android/server/sip/SipWakeLock.java | 14 +- java/com/android/server/sip/SipWakeupTimer.java | 61 +++-- 9 files changed, 499 insertions(+), 344 deletions(-) diff --git a/java/android/net/sip/SipAudioCall.java b/java/android/net/sip/SipAudioCall.java index 1d67055..ea943e9 100644 --- a/java/android/net/sip/SipAudioCall.java +++ b/java/android/net/sip/SipAudioCall.java @@ -25,17 +25,11 @@ 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.telephony.Rlog; import android.text.TextUtils; -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; /** * Handles an Internet audio call over SIP. You can instantiate this class with {@link SipManager}, @@ -60,7 +54,8 @@ import java.util.Map; * */ public class SipAudioCall { - private static final String TAG = SipAudioCall.class.getSimpleName(); + private static final String LOG_TAG = SipAudioCall.class.getSimpleName(); + private static final boolean DBG = true; private static final boolean RELEASE_SOCKET = true; private static final boolean DONT_RELEASE_SOCKET = false; private static final int SESSION_TIMEOUT = 5; // in seconds @@ -191,7 +186,6 @@ public class SipAudioCall { private boolean mMuted = false; private boolean mHold = false; - private SipProfile mPendingCallRequest; private WifiManager mWm; private WifiManager.WifiLock mWifiHighPerfLock; @@ -261,7 +255,7 @@ public class SipAudioCall { } } } catch (Throwable t) { - Log.e(TAG, "setListener()", t); + loge("setListener()", t); } } @@ -371,7 +365,7 @@ public class SipAudioCall { mAudioStream = new AudioStream(InetAddress.getByName( getLocalIp())); } catch (Throwable t) { - Log.i(TAG, "transferToNewSession(): " + t); + loge("transferToNewSession():", t); } } if (origin != null) origin.endCall(); @@ -382,26 +376,26 @@ public class SipAudioCall { return new SipSession.Listener() { @Override public void onCalling(SipSession session) { - Log.d(TAG, "calling... " + session); + if (DBG) log("onCalling: session=" + session); Listener listener = mListener; if (listener != null) { try { listener.onCalling(SipAudioCall.this); } catch (Throwable t) { - Log.i(TAG, "onCalling(): " + t); + loge("onCalling():", t); } } } @Override public void onRingingBack(SipSession session) { - Log.d(TAG, "sip call ringing back: " + session); + if (DBG) log("onRingingBackk: " + session); Listener listener = mListener; if (listener != null) { try { listener.onRingingBack(SipAudioCall.this); } catch (Throwable t) { - Log.i(TAG, "onRingingBack(): " + t); + loge("onRingingBack():", t); } } } @@ -424,7 +418,7 @@ public class SipAudioCall { String answer = createAnswer(sessionDescription).encode(); mSipSession.answerCall(answer, SESSION_TIMEOUT); } catch (Throwable e) { - Log.e(TAG, "onRinging()", e); + loge("onRinging():", e); session.endCall(); } } @@ -434,7 +428,7 @@ public class SipAudioCall { public void onCallEstablished(SipSession session, String sessionDescription) { mPeerSd = sessionDescription; - Log.v(TAG, "onCallEstablished()" + mPeerSd); + if (DBG) log("onCallEstablished(): " + mPeerSd); // TODO: how to notify the UI that the remote party is changed if ((mTransferringSession != null) @@ -452,14 +446,14 @@ public class SipAudioCall { listener.onCallEstablished(SipAudioCall.this); } } catch (Throwable t) { - Log.i(TAG, "onCallEstablished(): " + t); + loge("onCallEstablished(): ", t); } } } @Override public void onCallEnded(SipSession session) { - Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession); + if (DBG) log("onCallEnded: " + session + " mSipSession:" + mSipSession); // reset the trasnferring session if it is the one. if (session == mTransferringSession) { mTransferringSession = null; @@ -475,7 +469,7 @@ public class SipAudioCall { try { listener.onCallEnded(SipAudioCall.this); } catch (Throwable t) { - Log.i(TAG, "onCallEnded(): " + t); + loge("onCallEnded(): ", t); } } close(); @@ -483,13 +477,13 @@ public class SipAudioCall { @Override public void onCallBusy(SipSession session) { - Log.d(TAG, "sip call busy: " + session); + if (DBG) log("onCallBusy: " + session); Listener listener = mListener; if (listener != null) { try { listener.onCallBusy(SipAudioCall.this); } catch (Throwable t) { - Log.i(TAG, "onCallBusy(): " + t); + loge("onCallBusy(): ", t); } } close(false); @@ -498,7 +492,7 @@ public class SipAudioCall { @Override public void onCallChangeFailed(SipSession session, int errorCode, String message) { - Log.d(TAG, "sip call change failed: " + message); + if (DBG) log("onCallChangedFailed: " + message); mErrorCode = errorCode; mErrorMessage = message; Listener listener = mListener; @@ -507,7 +501,7 @@ public class SipAudioCall { listener.onError(SipAudioCall.this, mErrorCode, message); } catch (Throwable t) { - Log.i(TAG, "onCallBusy(): " + t); + loge("onCallBusy():", t); } } } @@ -542,8 +536,8 @@ public class SipAudioCall { @Override public void onCallTransferring(SipSession newSession, String sessionDescription) { - Log.v(TAG, "onCallTransferring mSipSession:" - + mSipSession + " newSession:" + newSession); + if (DBG) log("onCallTransferring: mSipSession=" + + mSipSession + " newSession=" + newSession); mTransferringSession = newSession; try { if (sessionDescription == null) { @@ -554,7 +548,7 @@ public class SipAudioCall { newSession.answerCall(answer, SESSION_TIMEOUT); } } catch (Throwable e) { - Log.e(TAG, "onCallTransferring()", e); + loge("onCallTransferring()", e); newSession.endCall(); } } @@ -562,7 +556,7 @@ public class SipAudioCall { } private void onError(int errorCode, String message) { - Log.d(TAG, "sip session error: " + if (DBG) log("onError: " + SipErrorCode.toString(errorCode) + ": " + message); mErrorCode = errorCode; mErrorMessage = message; @@ -571,7 +565,7 @@ public class SipAudioCall { try { listener.onError(this, errorCode, message); } catch (Throwable t) { - Log.i(TAG, "onError(): " + t); + loge("onError():", t); } } synchronized (this) { @@ -600,11 +594,11 @@ public class SipAudioCall { synchronized (this) { mSipSession = session; mPeerSd = sessionDescription; - Log.v(TAG, "attachCall()" + mPeerSd); + if (DBG) log("attachCall(): " + mPeerSd); try { session.setListener(createListener()); } catch (Throwable e) { - Log.e(TAG, "attachCall()", e); + loge("attachCall()", e); throwSipException(e); } } @@ -627,6 +621,7 @@ public class SipAudioCall { */ public void makeCall(SipProfile peerProfile, SipSession sipSession, int timeout) throws SipException { + if (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout); if (!SipManager.isVoipSupported(mContext)) { throw new SipException("VOIP API is not supported"); } @@ -640,6 +635,7 @@ public class SipAudioCall { sipSession.makeCall(peerProfile, createOffer().encode(), timeout); } catch (IOException e) { + loge("makeCall:", e); throw new SipException("makeCall()", e); } } @@ -650,6 +646,7 @@ public class SipAudioCall { * @throws SipException if the SIP service fails to end the call */ public void endCall() throws SipException { + if (DBG) log("endCall: mSipSession" + mSipSession); synchronized (this) { stopCall(RELEASE_SOCKET); mInCall = false; @@ -672,9 +669,11 @@ public class SipAudioCall { * @throws SipException if the SIP service fails to hold the call */ public void holdCall(int timeout) throws SipException { + if (DBG) log("holdCall: mSipSession" + mSipSession + " timeout=" + timeout); synchronized (this) { if (mHold) return; if (mSipSession == null) { + loge("holdCall:"); throw new SipException("Not in a call to hold call"); } mSipSession.changeCall(createHoldOffer().encode(), timeout); @@ -695,6 +694,7 @@ public class SipAudioCall { * @throws SipException if the SIP service fails to answer the call */ public void answerCall(int timeout) throws SipException { + if (DBG) log("answerCall: mSipSession" + mSipSession + " timeout=" + timeout); synchronized (this) { if (mSipSession == null) { throw new SipException("No call to answer"); @@ -704,6 +704,7 @@ public class SipAudioCall { getLocalIp())); mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); } catch (IOException e) { + loge("answerCall:", e); throw new SipException("answerCall()", e); } } @@ -722,6 +723,7 @@ public class SipAudioCall { * @throws SipException if the SIP service fails to unhold the call */ public void continueCall(int timeout) throws SipException { + if (DBG) log("continueCall: mSipSession" + mSipSession + " timeout=" + timeout); synchronized (this) { if (!mHold) return; mSipSession.changeCall(createContinueOffer().encode(), timeout); @@ -740,6 +742,7 @@ public class SipAudioCall { media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp); } media.setRtpPayload(127, "telephone-event/8000", "0-15"); + if (DBG) log("createOffer: offer=" + offer); return offer; } @@ -798,18 +801,22 @@ public class SipAudioCall { } } if (codec == null) { + loge("createAnswer: no suitable codes"); throw new IllegalStateException("Reject SDP: no suitable codecs"); } + if (DBG) log("createAnswer: answer=" + answer); return answer; } private SimpleSessionDescription createHoldOffer() { SimpleSessionDescription offer = createContinueOffer(); offer.setAttribute("sendonly", ""); + if (DBG) log("createHoldOffer: offer=" + offer); return offer; } private SimpleSessionDescription createContinueOffer() { + if (DBG) log("createContinueOffer"); SimpleSessionDescription offer = new SimpleSessionDescription(mSessionId, getLocalIp()); Media media = offer.newMedia( @@ -825,17 +832,17 @@ public class SipAudioCall { private void grabWifiHighPerfLock() { if (mWifiHighPerfLock == null) { - Log.v(TAG, "acquire wifi high perf lock"); + if (DBG) log("grabWifiHighPerfLock:"); mWifiHighPerfLock = ((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG); + .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG); mWifiHighPerfLock.acquire(); } } private void releaseWifiHighPerfLock() { if (mWifiHighPerfLock != null) { - Log.v(TAG, "release wifi high perf lock"); + if (DBG) log("releaseWifiHighPerfLock:"); mWifiHighPerfLock.release(); mWifiHighPerfLock = null; } @@ -912,7 +919,7 @@ public class SipAudioCall { AudioGroup audioGroup = getAudioGroup(); if ((audioGroup != null) && (mSipSession != null) && (SipSession.State.IN_CALL == getState())) { - Log.v(TAG, "send DTMF: " + code); + if (DBG) log("sendDtmf: code=" + code + " result=" + result); audioGroup.sendDtmf(code); } if (result != null) result.sendToTarget(); @@ -971,6 +978,7 @@ public class SipAudioCall { */ public void setAudioGroup(AudioGroup group) { synchronized (this) { + if (DBG) log("setAudioGroup: group=" + group); if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { mAudioStream.join(group); } @@ -997,8 +1005,8 @@ public class SipAudioCall { } private synchronized void startAudioInternal() throws UnknownHostException { + if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd); if (mPeerSd == null) { - Log.v(TAG, "startAudioInternal() mPeerSd = null"); throw new IllegalStateException("mPeerSd = null"); } @@ -1082,6 +1090,7 @@ public class SipAudioCall { // set audio group mode based on current audio configuration private void setAudioGroupMode() { AudioGroup audioGroup = getAudioGroup(); + if (DBG) log("setAudioGroupMode: audioGroup=" + audioGroup); if (audioGroup != null) { if (mHold) { audioGroup.setMode(AudioGroup.MODE_ON_HOLD); @@ -1096,7 +1105,7 @@ public class SipAudioCall { } private void stopCall(boolean releaseSocket) { - Log.d(TAG, "stop audiocall"); + if (DBG) log("stopCall: releaseSocket=" + releaseSocket); releaseWifiHighPerfLock(); if (mAudioStream != null) { mAudioStream.join(null); @@ -1120,7 +1129,15 @@ public class SipAudioCall { } } - private SipProfile getPeerProfile(SipSession session) { - return session.getPeerProfile(); + private void log(String s) { + Rlog.d(LOG_TAG, s); + } + + private void loge(String s) { + Rlog.e(LOG_TAG, s); + } + + private void loge(String s, Throwable t) { + Rlog.e(LOG_TAG, s, t); } } diff --git a/java/android/net/sip/SipManager.java b/java/android/net/sip/SipManager.java index 74c3672..a94232a 100644 --- a/java/android/net/sip/SipManager.java +++ b/java/android/net/sip/SipManager.java @@ -21,10 +21,9 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; -import android.util.Log; +import android.telephony.Rlog; import java.text.ParseException; @@ -591,7 +590,7 @@ public class SipManager { : session.getLocalProfile().getUriString()); } catch (Throwable e) { // SipService died? SIP stack died? - Log.w(TAG, "getUri(): " + e); + Rlog.e(TAG, "getUri(): ", e); return null; } } diff --git a/java/android/net/sip/SipSession.java b/java/android/net/sip/SipSession.java index e03cf9f..edbc66f 100644 --- a/java/android/net/sip/SipSession.java +++ b/java/android/net/sip/SipSession.java @@ -17,7 +17,7 @@ package android.net.sip; import android.os.RemoteException; -import android.util.Log; +import android.telephony.Rlog; /** * Represents a SIP session that is associated with a SIP dialog or a standalone @@ -242,7 +242,7 @@ public final class SipSession { try { realSession.setListener(createListener()); } catch (RemoteException e) { - Log.e(TAG, "SipSession.setListener(): " + e); + loge("SipSession.setListener:", e); } } } @@ -261,7 +261,7 @@ public final class SipSession { try { return mSession.getLocalIp(); } catch (RemoteException e) { - Log.e(TAG, "getLocalIp(): " + e); + loge("getLocalIp:", e); return "127.0.0.1"; } } @@ -275,7 +275,7 @@ public final class SipSession { try { return mSession.getLocalProfile(); } catch (RemoteException e) { - Log.e(TAG, "getLocalProfile(): " + e); + loge("getLocalProfile:", e); return null; } } @@ -290,7 +290,7 @@ public final class SipSession { try { return mSession.getPeerProfile(); } catch (RemoteException e) { - Log.e(TAG, "getPeerProfile(): " + e); + loge("getPeerProfile:", e); return null; } } @@ -305,7 +305,7 @@ public final class SipSession { try { return mSession.getState(); } catch (RemoteException e) { - Log.e(TAG, "getState(): " + e); + loge("getState:", e); return State.NOT_DEFINED; } } @@ -319,7 +319,7 @@ public final class SipSession { try { return mSession.isInCall(); } catch (RemoteException e) { - Log.e(TAG, "isInCall(): " + e); + loge("isInCall:", e); return false; } } @@ -333,7 +333,7 @@ public final class SipSession { try { return mSession.getCallId(); } catch (RemoteException e) { - Log.e(TAG, "getCallId(): " + e); + loge("getCallId:", e); return null; } } @@ -364,7 +364,7 @@ public final class SipSession { try { mSession.register(duration); } catch (RemoteException e) { - Log.e(TAG, "register(): " + e); + loge("register:", e); } } @@ -381,7 +381,7 @@ public final class SipSession { try { mSession.unregister(); } catch (RemoteException e) { - Log.e(TAG, "unregister(): " + e); + loge("unregister:", e); } } @@ -402,7 +402,7 @@ public final class SipSession { try { mSession.makeCall(callee, sessionDescription, timeout); } catch (RemoteException e) { - Log.e(TAG, "makeCall(): " + e); + loge("makeCall:", e); } } @@ -420,7 +420,7 @@ public final class SipSession { try { mSession.answerCall(sessionDescription, timeout); } catch (RemoteException e) { - Log.e(TAG, "answerCall(): " + e); + loge("answerCall:", e); } } @@ -436,7 +436,7 @@ public final class SipSession { try { mSession.endCall(); } catch (RemoteException e) { - Log.e(TAG, "endCall(): " + e); + loge("endCall:", e); } } @@ -453,7 +453,7 @@ public final class SipSession { try { mSession.changeCall(sessionDescription, timeout); } catch (RemoteException e) { - Log.e(TAG, "changeCall(): " + e); + loge("changeCall:", e); } } @@ -463,12 +463,14 @@ public final class SipSession { private ISipSessionListener createListener() { return new ISipSessionListener.Stub() { + @Override public void onCalling(ISipSession session) { if (mListener != null) { mListener.onCalling(SipSession.this); } } + @Override public void onRinging(ISipSession session, SipProfile caller, String sessionDescription) { if (mListener != null) { @@ -477,12 +479,14 @@ public final class SipSession { } } + @Override public void onRingingBack(ISipSession session) { if (mListener != null) { mListener.onRingingBack(SipSession.this); } } + @Override public void onCallEstablished(ISipSession session, String sessionDescription) { if (mListener != null) { @@ -491,18 +495,21 @@ public final class SipSession { } } + @Override public void onCallEnded(ISipSession session) { if (mListener != null) { mListener.onCallEnded(SipSession.this); } } + @Override public void onCallBusy(ISipSession session) { if (mListener != null) { mListener.onCallBusy(SipSession.this); } } + @Override public void onCallTransferring(ISipSession session, String sessionDescription) { if (mListener != null) { @@ -513,6 +520,7 @@ public final class SipSession { } } + @Override public void onCallChangeFailed(ISipSession session, int errorCode, String message) { if (mListener != null) { @@ -521,24 +529,28 @@ public final class SipSession { } } + @Override public void onError(ISipSession session, int errorCode, String message) { if (mListener != null) { mListener.onError(SipSession.this, errorCode, message); } } + @Override public void onRegistering(ISipSession session) { if (mListener != null) { mListener.onRegistering(SipSession.this); } } + @Override public void onRegistrationDone(ISipSession session, int duration) { if (mListener != null) { mListener.onRegistrationDone(SipSession.this, duration); } } + @Override public void onRegistrationFailed(ISipSession session, int errorCode, String message) { if (mListener != null) { @@ -547,6 +559,7 @@ public final class SipSession { } } + @Override public void onRegistrationTimeout(ISipSession session) { if (mListener != null) { mListener.onRegistrationTimeout(SipSession.this); @@ -554,4 +567,8 @@ public final class SipSession { } }; } + + private void loge(String s, Throwable t) { + Rlog.e(TAG, s, t); + } } diff --git a/java/com/android/server/sip/SipHelper.java b/java/com/android/server/sip/SipHelper.java index 113f007..c708be8 100644 --- a/java/com/android/server/sip/SipHelper.java +++ b/java/com/android/server/sip/SipHelper.java @@ -24,7 +24,7 @@ import gov.nist.javax.sip.header.extensions.ReferredByHeader; import gov.nist.javax.sip.header.extensions.ReplacesHeader; import android.net.sip.SipProfile; -import android.util.Log; +import android.telephony.Rlog; import java.text.ParseException; import java.util.ArrayList; @@ -46,9 +46,7 @@ import javax.sip.SipFactory; import javax.sip.SipProvider; import javax.sip.SipStack; import javax.sip.Transaction; -import javax.sip.TransactionAlreadyExistsException; import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; import javax.sip.TransactionState; import javax.sip.address.Address; import javax.sip.address.AddressFactory; @@ -73,8 +71,8 @@ import javax.sip.message.Response; */ class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean DEBUG_PING = false; + private static final boolean DBG = false; + private static final boolean DBG_PING = false; private SipStack mSipStack; private SipProvider mSipProvider; @@ -262,7 +260,7 @@ class SipHelper { ClientTransaction tid = responseEvent.getClientTransaction(); ClientTransaction ct = authenticationHelper.handleChallenge( responseEvent.getResponse(), tid, mSipProvider, 5); - if (DEBUG) Log.d(TAG, "send request with challenge response: " + if (DBG) log("send request with challenge response: " + ct.getRequest()); ct.sendRequest(); return ct; @@ -301,7 +299,7 @@ class SipHelper { "application", "sdp")); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); - if (DEBUG) Log.d(TAG, "send INVITE: " + request); + if (DBG) log("send INVITE: " + request); clientTransaction.sendRequest(); return clientTransaction; } catch (ParseException e) { @@ -326,7 +324,7 @@ class SipHelper { ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); - if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request); + if (DBG) log("send RE-INVITE: " + request); dialog.sendRequest(clientTransaction); return clientTransaction; } catch (ParseException e) { @@ -360,7 +358,7 @@ class SipHelper { ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); toHeader.setTag(tag); response.addHeader(toHeader); - if (DEBUG) Log.d(TAG, "send RINGING: " + response); + if (DBG) log("send RINGING: " + response); transaction.sendResponse(response); return transaction; } catch (ParseException e) { @@ -390,7 +388,7 @@ class SipHelper { } if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DEBUG) Log.d(TAG, "send OK: " + response); + if (DBG) log("send OK: " + response); inviteTransaction.sendResponse(response); } @@ -412,7 +410,7 @@ class SipHelper { } if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response); + if (DBG) log("send BUSY HERE: " + response); inviteTransaction.sendResponse(response); } } catch (ParseException e) { @@ -429,20 +427,20 @@ class SipHelper { long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) .getSeqNumber(); Request ack = dialog.createAck(cseq); - if (DEBUG) Log.d(TAG, "send ACK: " + ack); + if (DBG) log("send ACK: " + ack); dialog.sendAck(ack); } public void sendBye(Dialog dialog) throws SipException { Request byeRequest = dialog.createRequest(Request.BYE); - if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest); + if (DBG) log("send BYE: " + byeRequest); dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); } public void sendCancel(ClientTransaction inviteTransaction) throws SipException { Request cancelRequest = inviteTransaction.createCancel(); - if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest); + if (DBG) log("send CANCEL: " + cancelRequest); mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); } @@ -452,9 +450,9 @@ class SipHelper { Request request = event.getRequest(); Response response = mMessageFactory.createResponse( responseCode, request); - if (DEBUG && (!Request.OPTIONS.equals(request.getMethod()) - || DEBUG_PING)) { - Log.d(TAG, "send response: " + response); + if (DBG && (!Request.OPTIONS.equals(request.getMethod()) + || DBG_PING)) { + log("send response: " + response); } getServerTransaction(event).sendResponse(response); } catch (ParseException e) { @@ -474,7 +472,7 @@ class SipHelper { "message", "sipfrag")); request.addHeader(mHeaderFactory.createEventHeader( ReferencesHeader.REFER)); - if (DEBUG) Log.d(TAG, "send NOTIFY: " + request); + if (DBG) log("send NOTIFY: " + request); dialog.sendRequest(mSipProvider.getNewClientTransaction(request)); } catch (ParseException e) { throw new SipException("sendReferNotify()", e); @@ -486,7 +484,7 @@ class SipHelper { try { Response response = mMessageFactory.createResponse( Response.REQUEST_TERMINATED, inviteRequest); - if (DEBUG) Log.d(TAG, "send response: " + response); + if (DBG) log("send response: " + response); inviteTransaction.sendResponse(response); } catch (ParseException e) { throw new SipException("sendInviteRequestTerminated()", e); @@ -532,4 +530,8 @@ class SipHelper { private static String getCallId(Dialog dialog) { return dialog.getCallId().getCallId(); } + + private void log(String s) { + Rlog.d(TAG, s); + } } diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java index a477fd1..80fe68c 100644 --- a/java/com/android/server/sip/SipService.java +++ b/java/com/android/server/sip/SipService.java @@ -16,7 +16,6 @@ package com.android.server.sip; -import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -44,22 +43,15 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; +import android.telephony.Rlog; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; -import java.util.TreeSet; import java.util.concurrent.Executor; import javax.sip.SipException; @@ -68,7 +60,7 @@ import javax.sip.SipException; */ public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; - static final boolean DEBUG = false; + static final boolean DBG = true; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; @@ -82,7 +74,7 @@ public final class SipService extends ISipService.Stub { private WifiManager.WifiLock mWifiLock; private boolean mSipOnWifiOnly; - private IntervalMeasurementProcess mIntervalMeasurementProcess; + private SipKeepAliveProcessCallback mSipKeepAliveProcessCallback; private MyExecutor mExecutor = new MyExecutor(); @@ -107,12 +99,12 @@ public final class SipService extends ISipService.Stub { if (SipManager.isApiSupported(context)) { ServiceManager.addService("sip", new SipService(context)); context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); - if (DEBUG) Log.d(TAG, "SIP service started"); + if (DBG) slog("start:"); } } private SipService(Context context) { - if (DEBUG) Log.d(TAG, " service started!"); + if (DBG) log("SipService: started!"); mContext = context; mConnectivityReceiver = new ConnectivityReceiver(); @@ -128,6 +120,7 @@ public final class SipService extends ISipService.Stub { mTimer = new SipWakeupTimer(context, mExecutor); } + @Override public synchronized SipProfile[] getListOfProfiles() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -141,6 +134,7 @@ public final class SipService extends ISipService.Stub { return profiles.toArray(new SipProfile[profiles.size()]); } + @Override public synchronized void open(SipProfile localProfile) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -148,11 +142,12 @@ public final class SipService extends ISipService.Stub { try { createGroup(localProfile); } catch (SipException e) { - Log.e(TAG, "openToMakeCalls()", e); + loge("openToMakeCalls()", e); // TODO: how to send the exception back } } + @Override public synchronized void open3(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener) { @@ -160,11 +155,11 @@ public final class SipService extends ISipService.Stub { android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); if (incomingCallPendingIntent == null) { - Log.w(TAG, "incomingCallPendingIntent cannot be null; " + if (DBG) log("open3: incomingCallPendingIntent cannot be null; " + "the profile is not opened"); return; } - if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " + if (DBG) log("open3: " + localProfile.getUriString() + ": " + incomingCallPendingIntent + ": " + listener); try { SipSessionGroupExt group = createGroup(localProfile, @@ -174,7 +169,7 @@ public final class SipService extends ISipService.Stub { updateWakeLocks(); } } catch (SipException e) { - Log.e(TAG, "openToReceiveCalls()", e); + loge("open3:", e); // TODO: how to send the exception back } } @@ -192,13 +187,14 @@ public final class SipService extends ISipService.Stub { return (Binder.getCallingUid() == Process.PHONE_UID); } + @Override public synchronized void close(String localProfileUri) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); SipSessionGroupExt group = mSipGroups.get(localProfileUri); if (group == null) return; if (!isCallerCreatorOrRadio(group)) { - Log.w(TAG, "only creator or radio can close this profile"); + if (DBG) log("only creator or radio can close this profile"); return; } @@ -209,6 +205,7 @@ public final class SipService extends ISipService.Stub { updateWakeLocks(); } + @Override public synchronized boolean isOpened(String localProfileUri) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -217,11 +214,12 @@ public final class SipService extends ISipService.Stub { if (isCallerCreatorOrRadio(group)) { return true; } else { - Log.w(TAG, "only creator or radio can query on the profile"); + if (DBG) log("only creator or radio can query on the profile"); return false; } } + @Override public synchronized boolean isRegistered(String localProfileUri) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -230,11 +228,12 @@ public final class SipService extends ISipService.Stub { if (isCallerCreatorOrRadio(group)) { return group.isRegistered(); } else { - Log.w(TAG, "only creator or radio can query on the profile"); + if (DBG) log("only creator or radio can query on the profile"); return false; } } + @Override public synchronized void setRegistrationListener(String localProfileUri, ISipSessionListener listener) { mContext.enforceCallingOrSelfPermission( @@ -244,25 +243,31 @@ public final class SipService extends ISipService.Stub { if (isCallerCreator(group)) { group.setListener(listener); } else { - Log.w(TAG, "only creator can set listener on the profile"); + if (DBG) log("only creator can set listener on the profile"); } } + @Override public synchronized ISipSession createSession(SipProfile localProfile, ISipSessionListener listener) { + if (DBG) log("createSession: profile" + localProfile); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); localProfile.setCallingUid(Binder.getCallingUid()); - if (mNetworkType == -1) return null; + if (mNetworkType == -1) { + if (DBG) log("createSession: mNetworkType==-1 ret=null"); + return null; + } try { SipSessionGroupExt group = createGroup(localProfile); return group.createSession(listener); } catch (SipException e) { - if (DEBUG) Log.d(TAG, "createSession()", e); + if (DBG) loge("createSession;", e); return null; } } + @Override public synchronized ISipSession getPendingSession(String callId) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_SIP, null); @@ -276,7 +281,7 @@ public final class SipService extends ISipService.Stub { s.connect(InetAddress.getByName("192.168.1.1"), 80); return s.getLocalAddress().getHostAddress(); } catch (IOException e) { - if (DEBUG) Log.d(TAG, "determineLocalIp()", e); + if (DBG) loge("determineLocalIp()", e); // dont do anything; there should be a connectivity change going return null; } @@ -317,7 +322,7 @@ public final class SipService extends ISipService.Stub { } private void notifyProfileAdded(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); + if (DBG) log("notify: profile added: " + localProfile); Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); mContext.sendBroadcast(intent); @@ -327,7 +332,7 @@ public final class SipService extends ISipService.Stub { } private void notifyProfileRemoved(SipProfile localProfile) { - if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile); + if (DBG) log("notify: profile removed: " + localProfile); Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); mContext.sendBroadcast(intent); @@ -337,9 +342,9 @@ public final class SipService extends ISipService.Stub { } private void stopPortMappingMeasurement() { - if (mIntervalMeasurementProcess != null) { - mIntervalMeasurementProcess.stop(); - mIntervalMeasurementProcess = null; + if (mSipKeepAliveProcessCallback != null) { + mSipKeepAliveProcessCallback.stop(); + mSipKeepAliveProcessCallback = null; } } @@ -351,10 +356,10 @@ public final class SipService extends ISipService.Stub { private void startPortMappingLifetimeMeasurement( SipProfile localProfile, int maxInterval) { - if ((mIntervalMeasurementProcess == null) + if ((mSipKeepAliveProcessCallback == null) && (mKeepAliveInterval == -1) && isBehindNAT(mLocalIp)) { - Log.d(TAG, "start NAT port mapping timeout measurement on " + if (DBG) log("startPortMappingLifetimeMeasurement: profile=" + localProfile.getUriString()); int minInterval = mLastGoodKeepAliveInterval; @@ -363,11 +368,11 @@ public final class SipService extends ISipService.Stub { // to the default min minInterval = mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; - Log.d(TAG, " reset min interval to " + minInterval); + log(" reset min interval to " + minInterval); } - mIntervalMeasurementProcess = new IntervalMeasurementProcess( + mSipKeepAliveProcessCallback = new SipKeepAliveProcessCallback( localProfile, minInterval, maxInterval); - mIntervalMeasurementProcess.start(); + mSipKeepAliveProcessCallback.start(); } } @@ -382,10 +387,10 @@ public final class SipService extends ISipService.Stub { try { cleanUpPendingSessions(); mPendingSessions.put(session.getCallId(), session); - if (DEBUG) Log.d(TAG, "#pending sess=" + mPendingSessions.size()); + if (DBG) log("#pending sess=" + mPendingSessions.size()); } catch (RemoteException e) { // should not happen with a local call - Log.e(TAG, "addPendingSession()", e); + loge("addPendingSession()", e); } } @@ -405,7 +410,7 @@ public final class SipService extends ISipService.Stub { String callId = ringingSession.getCallId(); for (SipSessionGroupExt group : mSipGroups.values()) { if ((group != ringingGroup) && group.containsSession(callId)) { - if (DEBUG) Log.d(TAG, "call self: " + if (DBG) log("call self: " + ringingSession.getLocalProfile().getUriString() + " -> " + group.getLocalProfile().getUriString()); return true; @@ -428,31 +433,36 @@ public final class SipService extends ISipService.Stub { private boolean isBehindNAT(String address) { try { + // TODO: How is isBehindNAT used and why these constanst address: + // 10.x.x.x | 192.168.x.x | 172.16.x.x .. 172.19.x.x byte[] d = InetAddress.getByName(address).getAddress(); if ((d[0] == 10) || - (((0x000000FF & ((int)d[0])) == 172) && - ((0x000000F0 & ((int)d[1])) == 16)) || - (((0x000000FF & ((int)d[0])) == 192) && - ((0x000000FF & ((int)d[1])) == 168))) { + (((0x000000FF & d[0]) == 172) && + ((0x000000F0 & d[1]) == 16)) || + (((0x000000FF & d[0]) == 192) && + ((0x000000FF & d[1]) == 168))) { return true; } } catch (UnknownHostException e) { - Log.e(TAG, "isBehindAT()" + address, e); + loge("isBehindAT()" + address, e); } return false; } private class SipSessionGroupExt extends SipSessionAdapter { + private static final String SSGE_TAG = "SipSessionGroupExt"; + private static final boolean SSGE_DBG = true; private SipSessionGroup mSipGroup; private PendingIntent mIncomingCallPendingIntent; private boolean mOpenedToReceiveCalls; - private AutoRegistrationProcess mAutoRegistration = - new AutoRegistrationProcess(); + private SipAutoReg mAutoRegistration = + new SipAutoReg(); public SipSessionGroupExt(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener) throws SipException { + if (SSGE_DBG) log("SipSessionGroupExt: profile=" + localProfile); mSipGroup = new SipSessionGroup(duplicate(localProfile), localProfile.getPassword(), mTimer, mMyWakeLock); mIncomingCallPendingIntent = incomingCallPendingIntent; @@ -481,7 +491,7 @@ public final class SipService extends ISipService.Stub { try { return new SipProfile.Builder(p).setPassword("*").build(); } catch (Exception e) { - Log.wtf(TAG, "duplicate()", e); + loge("duplicate()", e); throw new RuntimeException("duplicate profile", e); } } @@ -500,20 +510,21 @@ public final class SipService extends ISipService.Stub { mSipGroup.openToReceiveCalls(this); mAutoRegistration.start(mSipGroup); } - if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " + if (SSGE_DBG) log("openToReceiveCalls: " + getUri() + ": " + mIncomingCallPendingIntent); } public void onConnectivityChanged(boolean connected) throws SipException { + if (SSGE_DBG) { + log("onConnectivityChanged: connected=" + connected + " uri=" + + getUri() + ": " + mIncomingCallPendingIntent); + } mSipGroup.onConnectivityChanged(); if (connected) { mSipGroup.reset(); if (mOpenedToReceiveCalls) openToReceiveCalls(); } else { - // close mSipGroup but remember mOpenedToReceiveCalls - if (DEBUG) Log.d(TAG, " close auto reg temporarily: " - + getUri() + ": " + mIncomingCallPendingIntent); mSipGroup.close(); mAutoRegistration.stop(); } @@ -523,23 +534,23 @@ public final class SipService extends ISipService.Stub { mOpenedToReceiveCalls = false; mSipGroup.close(); mAutoRegistration.stop(); - if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " - + mIncomingCallPendingIntent); + if (SSGE_DBG) log("close: " + getUri() + ": " + mIncomingCallPendingIntent); } public ISipSession createSession(ISipSessionListener listener) { + if (SSGE_DBG) log("createSession"); return mSipGroup.createSession(listener); } @Override public void onRinging(ISipSession s, SipProfile caller, String sessionDescription) { - if (DEBUG) Log.d(TAG, "<<<<< onRinging()"); SipSessionGroup.SipSessionImpl session = (SipSessionGroup.SipSessionImpl) s; synchronized (SipService.this) { try { if (!isRegistered() || callingSelf(this, session)) { + if (SSGE_DBG) log("onRinging: end notReg or self"); session.endCall(); return; } @@ -548,13 +559,13 @@ public final class SipService extends ISipService.Stub { addPendingSession(session); Intent intent = SipManager.createIncomingCallBroadcast( session.getCallId(), sessionDescription); - if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " + if (SSGE_DBG) log("onRinging: uri=" + getUri() + ": " + caller.getUri() + ": " + session.getCallId() + " " + mIncomingCallPendingIntent); mIncomingCallPendingIntent.send(mContext, SipManager.INCOMING_CALL_RESULT_CODE, intent); } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "pendingIntent is canceled, drop incoming call"); + loge("onRinging: pendingIntent is canceled, drop incoming call", e); session.endCall(); } } @@ -563,7 +574,7 @@ public final class SipService extends ISipService.Stub { @Override public void onError(ISipSession session, int errorCode, String message) { - if (DEBUG) Log.d(TAG, "sip session error: " + if (SSGE_DBG) log("onError: errorCode=" + errorCode + " desc=" + SipErrorCode.toString(errorCode) + ": " + message); } @@ -578,14 +589,23 @@ public final class SipService extends ISipService.Stub { private String getUri() { return mSipGroup.getLocalProfileUri(); } + + private void log(String s) { + Rlog.d(SSGE_TAG, s); + } + + private void loge(String s, Throwable t) { + Rlog.e(SSGE_TAG, s, t); + } + } - private class IntervalMeasurementProcess implements Runnable, + private class SipKeepAliveProcessCallback implements Runnable, SipSessionGroup.KeepAliveProcessCallback { - private static final String TAG = "SipKeepAliveInterval"; + private static final String SKAI_TAG = "SipKeepAliveProcessCallback"; + private static final boolean SKAI_DBG = true; private static final int MIN_INTERVAL = 5; // in seconds private static final int PASS_THRESHOLD = 10; - private static final int MAX_RETRY_COUNT = 5; private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds private SipProfile mLocalProfile; private SipSessionGroupExt mGroup; @@ -595,7 +615,7 @@ public final class SipService extends ISipService.Stub { private int mInterval; private int mPassCount; - public IntervalMeasurementProcess(SipProfile localProfile, + public SipKeepAliveProcessCallback(SipProfile localProfile, int minInterval, int maxInterval) { mMaxInterval = maxInterval; mMinInterval = minInterval; @@ -613,13 +633,13 @@ public final class SipService extends ISipService.Stub { // Don't start measurement if the interval is too small if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) { - Log.w(TAG, "measurement aborted; interval=[" + + if (SKAI_DBG) log("start: measurement aborted; interval=[" + mMinInterval + "," + mMaxInterval + "]"); return; } try { - Log.d(TAG, "start measurement w interval=" + mInterval); + if (SKAI_DBG) log("start: interval=" + mInterval); mGroup = new SipSessionGroupExt(mLocalProfile, null, null); // TODO: remove this line once SipWakeupTimer can better handle @@ -646,6 +666,7 @@ public final class SipService extends ISipService.Stub { mGroup = null; } mTimer.cancel(this); + if (SKAI_DBG) log("stop"); } } @@ -654,13 +675,13 @@ public final class SipService extends ISipService.Stub { // Return immediately if the measurement process is stopped if (mSession == null) return; - Log.d(TAG, "restart measurement w interval=" + mInterval); + if (SKAI_DBG) log("restart: interval=" + mInterval); try { mSession.stopKeepAliveProcess(); mPassCount = 0; mSession.startKeepAliveProcess(mInterval, this); } catch (SipException e) { - Log.e(TAG, "restart()", e); + loge("restart", e); } } } @@ -681,8 +702,8 @@ public final class SipService extends ISipService.Stub { mLastGoodKeepAliveInterval = mKeepAliveInterval; } mKeepAliveInterval = mMinInterval = mInterval; - if (DEBUG) { - Log.d(TAG, "measured good keepalive interval: " + if (SKAI_DBG) { + log("onResponse: portChanged=" + portChanged + " mKeepAliveInterval=" + mKeepAliveInterval); } onKeepAliveIntervalChanged(); @@ -698,16 +719,16 @@ public final class SipService extends ISipService.Stub { // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL // will be conducted. mKeepAliveInterval = mMinInterval; - if (DEBUG) { - Log.d(TAG, "measured keepalive interval: " + if (SKAI_DBG) { + log("onResponse: checkTermination mKeepAliveInterval=" + mKeepAliveInterval); } } else { // calculate the new interval and continue. mInterval = (mMaxInterval + mMinInterval) / 2; - if (DEBUG) { - Log.d(TAG, "current interval: " + mKeepAliveInterval - + ", test new interval: " + mInterval); + if (SKAI_DBG) { + log("onResponse: mKeepAliveInterval=" + mKeepAliveInterval + + ", new mInterval=" + mInterval); } restart(); } @@ -717,7 +738,7 @@ public final class SipService extends ISipService.Stub { // SipSessionGroup.KeepAliveProcessCallback @Override public void onError(int errorCode, String description) { - Log.w(TAG, "interval measurement error: " + description); + if (SKAI_DBG) loge("onError: errorCode=" + errorCode + " desc=" + description); restartLater(); } @@ -735,12 +756,25 @@ public final class SipService extends ISipService.Stub { mTimer.set(interval * 1000, this); } } + + private void log(String s) { + Rlog.d(SKAI_TAG, s); + } + + private void loge(String s) { + Rlog.d(SKAI_TAG, s); + } + + private void loge(String s, Throwable t) { + Rlog.d(SKAI_TAG, s, t); + } } - private class AutoRegistrationProcess extends SipSessionAdapter + private class SipAutoReg extends SipSessionAdapter implements Runnable, SipSessionGroup.KeepAliveProcessCallback { + private String SAR_TAG; + private static final boolean SAR_DBG = true; private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10; - private String TAG = "SipAutoReg"; private SipSessionGroup.SipSessionImpl mSession; private SipSessionGroup.SipSessionImpl mKeepAliveSession; @@ -754,10 +788,6 @@ public final class SipService extends ISipService.Stub { private int mKeepAliveSuccessCount = 0; - private String getAction() { - return toString(); - } - public void start(SipSessionGroup group) { if (!mRunning) { mRunning = true; @@ -772,12 +802,13 @@ public final class SipService extends ISipService.Stub { // in registration to avoid adding duplicate entries to server mMyWakeLock.acquire(mSession); mSession.unregister(); - TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString(); + SAR_TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString(); + if (SAR_DBG) log("start: group=" + group); } } private void startKeepAliveProcess(int interval) { - if (DEBUG) Log.d(TAG, "start keepalive w interval=" + interval); + if (SAR_DBG) log("startKeepAliveProcess: interval=" + interval); if (mKeepAliveSession == null) { mKeepAliveSession = mSession.duplicate(); } else { @@ -786,8 +817,7 @@ public final class SipService extends ISipService.Stub { try { mKeepAliveSession.startKeepAliveProcess(interval, this); } catch (SipException e) { - Log.e(TAG, "failed to start keepalive w interval=" + interval, - e); + loge("startKeepAliveProcess: interval=" + interval, e); } } @@ -806,17 +836,19 @@ public final class SipService extends ISipService.Stub { if (portChanged) { int interval = getKeepAliveInterval(); if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) { - Log.i(TAG, "keepalive doesn't work with interval " - + interval + ", past success count=" - + mKeepAliveSuccessCount); + if (SAR_DBG) { + log("onResponse: keepalive doesn't work with interval " + + interval + ", past success count=" + + mKeepAliveSuccessCount); + } if (interval > DEFAULT_KEEPALIVE_INTERVAL) { restartPortMappingLifetimeMeasurement( mSession.getLocalProfile(), interval); mKeepAliveSuccessCount = 0; } } else { - if (DEBUG) { - Log.i(TAG, "keep keepalive going with interval " + if (SAR_DBG) { + log("keep keepalive going with interval " + interval + ", past success count=" + mKeepAliveSuccessCount); } @@ -847,8 +879,8 @@ public final class SipService extends ISipService.Stub { // SipSessionGroup.KeepAliveProcessCallback @Override public void onError(int errorCode, String description) { - if (DEBUG) { - Log.e(TAG, "keepalive error: " + description); + if (SAR_DBG) { + loge("onError: errorCode=" + errorCode + " desc=" + description); } onResponse(true); // re-register immediately } @@ -872,8 +904,8 @@ public final class SipService extends ISipService.Stub { public void onKeepAliveIntervalChanged() { if (mKeepAliveSession != null) { int newInterval = getKeepAliveInterval(); - if (DEBUG) { - Log.v(TAG, "restart keepalive w interval=" + newInterval); + if (SAR_DBG) { + log("onKeepAliveIntervalChanged: interval=" + newInterval); } mKeepAliveSuccessCount = 0; startKeepAliveProcess(newInterval); @@ -916,7 +948,7 @@ public final class SipService extends ISipService.Stub { String.valueOf(state)); } } catch (Throwable t) { - Log.w(TAG, "setListener(): " + t); + loge("setListener: ", t); } } } @@ -933,7 +965,7 @@ public final class SipService extends ISipService.Stub { mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; - if (DEBUG) Log.d(TAG, "registering"); + if (SAR_DBG) log("run: registering"); if (mNetworkType != -1) { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); @@ -942,7 +974,7 @@ public final class SipService extends ISipService.Stub { } private void restart(int duration) { - Log.d(TAG, "Refresh registration " + duration + "s later."); + if (SAR_DBG) log("restart: duration=" + duration + "s later."); mTimer.cancel(this); mTimer.set(duration * 1000, this); } @@ -959,7 +991,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRegistering(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistering(): " + session); + if (SAR_DBG) log("onRegistering: " + session); synchronized (SipService.this) { if (notCurrentSession(session)) return; @@ -979,7 +1011,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRegistrationDone(ISipSession session, int duration) { - if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); + if (SAR_DBG) log("onRegistrationDone: " + session); synchronized (SipService.this) { if (notCurrentSession(session)) return; @@ -1008,7 +1040,7 @@ public final class SipService extends ISipService.Stub { } else { mRegistered = false; mExpiryTime = -1L; - if (DEBUG) Log.d(TAG, "Refresh registration immediately"); + if (SAR_DBG) log("Refresh registration immediately"); run(); } } @@ -1017,7 +1049,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRegistrationFailed(ISipSession session, int errorCode, String message) { - if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " + if (SAR_DBG) log("onRegistrationFailed: " + session + ": " + SipErrorCode.toString(errorCode) + ": " + message); synchronized (SipService.this) { if (notCurrentSession(session)) return; @@ -1025,7 +1057,7 @@ public final class SipService extends ISipService.Stub { switch (errorCode) { case SipErrorCode.INVALID_CREDENTIALS: case SipErrorCode.SERVER_UNREACHABLE: - if (DEBUG) Log.d(TAG, " pause auto-registration"); + if (SAR_DBG) log(" pause auto-registration"); stop(); break; default: @@ -1041,7 +1073,7 @@ public final class SipService extends ISipService.Stub { @Override public void onRegistrationTimeout(ISipSession session) { - if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); + if (SAR_DBG) log("onRegistrationTimeout: " + session); synchronized (SipService.this) { if (notCurrentSession(session)) return; @@ -1053,9 +1085,22 @@ public final class SipService extends ISipService.Stub { } private void restartLater() { + if (SAR_DBG) loge("restartLater"); mRegistered = false; restart(backoffDuration()); } + + private void log(String s) { + Rlog.d(SAR_TAG, s); + } + + private void loge(String s) { + Rlog.e(SAR_TAG, s); + } + + private void loge(String s, Throwable e) { + Rlog.e(SAR_TAG, s, e); + } } private class ConnectivityReceiver extends BroadcastReceiver { @@ -1068,6 +1113,7 @@ public final class SipService extends ISipService.Stub { // Run the handler in MyExecutor to be protected by wake lock mExecutor.execute(new Runnable() { + @Override public void run() { onConnectivityChanged(info); } @@ -1079,12 +1125,12 @@ public final class SipService extends ISipService.Stub { private void registerReceivers() { mContext.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - if (DEBUG) Log.d(TAG, " +++ register receivers"); + if (DBG) log("registerReceivers:"); } private void unregisterReceivers() { mContext.unregisterReceiver(mConnectivityReceiver); - if (DEBUG) Log.d(TAG, " --- unregister receivers"); + if (DBG) log("unregisterReceivers:"); // Reset variables maintained by ConnectivityReceiver. mWifiLock.release(); @@ -1131,10 +1177,11 @@ public final class SipService extends ISipService.Stub { // Ignore the event if the current active network is not changed. if (mNetworkType == networkType) { + // TODO: Maybe we need to send seq/generation number return; } - if (DEBUG) { - Log.d(TAG, "onConnectivityChanged(): " + mNetworkType + + if (DBG) { + log("onConnectivityChanged: " + mNetworkType + " -> " + networkType); } @@ -1158,7 +1205,7 @@ public final class SipService extends ISipService.Stub { } updateWakeLocks(); } catch (SipException e) { - Log.e(TAG, "onConnectivityChanged()", e); + loge("onConnectivityChanged()", e); } } @@ -1186,7 +1233,7 @@ public final class SipService extends ISipService.Stub { if (msg.obj instanceof Runnable) { executeInternal((Runnable) msg.obj); } else { - Log.w(TAG, "can't handle msg: " + msg); + if (DBG) log("handleMessage: not Runnable ignore msg=" + msg); } } @@ -1194,10 +1241,22 @@ public final class SipService extends ISipService.Stub { try { task.run(); } catch (Throwable t) { - Log.e(TAG, "run task: " + task, t); + loge("run task: " + task, t); } finally { mMyWakeLock.release(task); } } } + + private void log(String s) { + Rlog.d(TAG, s); + } + + private static void slog(String s) { + Rlog.d(TAG, s); + } + + private void loge(String s, Throwable e) { + Rlog.e(TAG, s, e); + } } diff --git a/java/com/android/server/sip/SipSessionGroup.java b/java/com/android/server/sip/SipSessionGroup.java index 6acd456..e820f35 100644 --- a/java/com/android/server/sip/SipSessionGroup.java +++ b/java/com/android/server/sip/SipSessionGroup.java @@ -35,7 +35,7 @@ import android.net.sip.SipProfile; import android.net.sip.SipSession; import android.net.sip.SipSessionAdapter; import android.text.TextUtils; -import android.util.Log; +import android.telephony.Rlog; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -43,7 +43,6 @@ import java.net.DatagramSocket; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.ParseException; -import java.util.Collection; import java.util.EventObject; import java.util.HashMap; import java.util.Map; @@ -53,7 +52,6 @@ import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; -import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; @@ -65,9 +63,7 @@ import javax.sip.SipProvider; import javax.sip.SipStack; import javax.sip.TimeoutEvent; import javax.sip.Transaction; -import javax.sip.TransactionState; import javax.sip.TransactionTerminatedEvent; -import javax.sip.TransactionUnavailableException; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; @@ -88,8 +84,8 @@ import javax.sip.message.Response; */ class SipSessionGroup implements SipListener { private static final String TAG = "SipSession"; - private static final boolean DEBUG = false; - private static final boolean DEBUG_PING = false; + private static final boolean DBG = false; + private static final boolean DBG_PING = false; private static final String ANONYMOUS = "anonymous"; // Limit the size of thread pool to 1 for the order issue when the phone is // waken up from sleep and there are many packets to be processed in the SIP @@ -105,9 +101,6 @@ class SipSessionGroup implements SipListener { private static final EventObject DEREGISTER = new EventObject("Deregister"); private static final EventObject END_CALL = new EventObject("End call"); - private static final EventObject HOLD_CALL = new EventObject("Hold call"); - private static final EventObject CONTINUE_CALL - = new EventObject("Continue call"); private final SipProfile mLocalProfile; private final String mPassword; @@ -133,7 +126,7 @@ class SipSessionGroup implements SipListener { /** * @param profile the local profile with password crossed out * @param password the password of the profile - * @throws IOException if cannot assign requested address + * @throws SipException if cannot assign requested address */ public SipSessionGroup(SipProfile profile, String password, SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException { @@ -206,7 +199,7 @@ class SipSessionGroup implements SipListener { throw new SipException("failed to initialize SIP stack", e); } - Log.d(TAG, " start stack for " + mLocalProfile.getUriString()); + if (DBG) log("reset: start stack for " + mLocalProfile.getUriString()); mSipStack.start(); } @@ -224,8 +217,8 @@ class SipSessionGroup implements SipListener { } synchronized void resetExternalAddress() { - if (DEBUG) { - Log.d(TAG, " reset external addr on " + mSipStack); + if (DBG) { + log("resetExternalAddress: " + mSipStack); } mExternalIp = null; mExternalPort = 0; @@ -244,7 +237,7 @@ class SipSessionGroup implements SipListener { } public synchronized void close() { - Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); + if (DBG) log("close: " + mLocalProfile.getUriString()); onConnectivityChanged(); mSessionMap.clear(); closeToNotReceiveCalls(); @@ -285,10 +278,10 @@ class SipSessionGroup implements SipListener { String key = SipHelper.getCallId(event); SipSessionImpl session = mSessionMap.get(key); if ((session != null) && isLoggable(session)) { - Log.d(TAG, "session key from event: " + key); - Log.d(TAG, "active sessions:"); + if (DBG) log("getSipSession: event=" + key); + if (DBG) log("getSipSession: active sessions:"); for (String k : mSessionMap.keySet()) { - Log.d(TAG, " ..." + k + ": " + mSessionMap.get(k)); + if (DBG) log("getSipSession: ..." + k + ": " + mSessionMap.get(k)); } } return ((session != null) ? session : mCallReceiverSession); @@ -299,9 +292,9 @@ class SipSessionGroup implements SipListener { String key = newSession.getCallId(); mSessionMap.put(key, newSession); if (isLoggable(newSession)) { - Log.d(TAG, "+++ add a session with key: '" + key + "'"); + if (DBG) log("addSipSession: key='" + key + "'"); for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); + if (DBG) log("addSipSession: " + k + ": " + mSessionMap.get(k)); } } } @@ -312,7 +305,7 @@ class SipSessionGroup implements SipListener { SipSessionImpl s = mSessionMap.remove(key); // sanity check if ((s != null) && (s != session)) { - Log.w(TAG, "session " + session + " is not associated with key '" + if (DBG) log("removeSession: " + session + " is not associated with key '" + key + "'"); mSessionMap.put(key, s); for (Map.Entry entry @@ -325,16 +318,17 @@ class SipSessionGroup implements SipListener { } if ((s != null) && isLoggable(s)) { - Log.d(TAG, "remove session " + session + " @key '" + key + "'"); + if (DBG) log("removeSession: " + session + " @key '" + key + "'"); for (String k : mSessionMap.keySet()) { - Log.d(TAG, " " + k + ": " + mSessionMap.get(k)); + if (DBG) log("removeSession: " + k + ": " + mSessionMap.get(k)); } } } + @Override public void processRequest(final RequestEvent event) { if (isRequestEvent(Request.INVITE, event)) { - if (DEBUG) Log.d(TAG, "<<<<< got INVITE, thread:" + if (DBG) log("processRequest: mWakeLock.acquire got INVITE, thread:" + Thread.currentThread()); // Acquire a wake lock and keep it for WAKE_LOCK_HOLDING_TIME; // should be large enough to bring up the app. @@ -343,22 +337,27 @@ class SipSessionGroup implements SipListener { process(event); } + @Override public void processResponse(ResponseEvent event) { process(event); } + @Override public void processIOException(IOExceptionEvent event) { process(event); } + @Override public void processTimeout(TimeoutEvent event) { process(event); } + @Override public void processTransactionTerminated(TransactionTerminatedEvent event) { process(event); } + @Override public void processDialogTerminated(DialogTerminatedEvent event) { process(event); } @@ -369,11 +368,11 @@ class SipSessionGroup implements SipListener { boolean isLoggable = isLoggable(session, event); boolean processed = (session != null) && session.process(event); if (isLoggable && processed) { - Log.d(TAG, "new state after: " + log("process: event new state after: " + SipSession.State.toString(session.mState)); } } catch (Throwable e) { - Log.w(TAG, "event process error: " + event, getRootCause(e)); + loge("process: error event=" + event, getRootCause(e)); session.onError(e); } } @@ -404,8 +403,8 @@ class SipSessionGroup implements SipListener { if ((rport > 0) && (externalIp != null)) { mExternalIp = externalIp; mExternalPort = rport; - if (DEBUG) { - Log.d(TAG, " got external addr " + externalIp + ":" + rport + if (DBG) { + log("extractExternalAddress: external addr " + externalIp + ":" + rport + " on " + mSipStack); } } @@ -436,6 +435,9 @@ class SipSessionGroup implements SipListener { } private class SipSessionCallReceiverImpl extends SipSessionImpl { + private static final String SSCRI_TAG = "SipSessionCallReceiverImpl"; + private static final boolean SSCRI_DBG = true; + public SipSessionCallReceiverImpl(ISipSessionListener listener) { super(listener); } @@ -473,8 +475,8 @@ class SipSessionGroup implements SipListener { SipSessionImpl newSession = null; if (replaces != null) { int response = processInviteWithReplaces(event, replaces); - if (DEBUG) { - Log.v(TAG, "ReplacesHeader: " + replaces + if (SSCRI_DBG) { + log("processNewInviteRequest: " + replaces + " response=" + response); } if (response == Response.OK) { @@ -501,10 +503,11 @@ class SipSessionGroup implements SipListener { if (newSession != null) addSipSession(newSession); } + @Override public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + if (isLoggable(this, evt)) log("process: " + this + ": " + SipSession.State.toString(mState) + ": processing " - + log(evt)); + + logEvt(evt)); if (isRequestEvent(Request.INVITE, evt)) { processNewInviteRequest((RequestEvent) evt); return true; @@ -515,6 +518,10 @@ class SipSessionGroup implements SipListener { return false; } } + + private void log(String s) { + Rlog.d(SSCRI_TAG, s); + } } static interface KeepAliveProcessCallback { @@ -524,6 +531,9 @@ class SipSessionGroup implements SipListener { } class SipSessionImpl extends ISipSession.Stub { + private static final String SSI_TAG = "SipSessionImpl"; + private static final boolean SSI_DBG = true; + SipProfile mPeerProfile; SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); int mState = SipSession.State.READY_TO_CALL; @@ -536,9 +546,9 @@ class SipSessionGroup implements SipListener { SessionTimer mSessionTimer; int mAuthenticationRetryCount; - private KeepAliveProcess mKeepAliveProcess; + private SipKeepAlive mSipKeepAlive; - private SipSessionImpl mKeepAliveSession; + private SipSessionImpl mSipSessionImpl; // the following three members are used for handling refer request. SipSessionImpl mReferSession; @@ -551,6 +561,7 @@ class SipSessionGroup implements SipListener { void start(final int timeout) { new Thread(new Runnable() { + @Override public void run() { sleep(timeout); if (mRunning) timeout(); @@ -573,7 +584,7 @@ class SipSessionGroup implements SipListener { try { this.wait(timeout * 1000); } catch (InterruptedException e) { - Log.e(TAG, "session timer interrupted!"); + loge("session timer interrupted!", e); } } } @@ -617,28 +628,33 @@ class SipSessionGroup implements SipListener { cancelSessionTimer(); - if (mKeepAliveSession != null) { - mKeepAliveSession.stopKeepAliveProcess(); - mKeepAliveSession = null; + if (mSipSessionImpl != null) { + mSipSessionImpl.stopKeepAliveProcess(); + mSipSessionImpl = null; } } + @Override public boolean isInCall() { return mInCall; } + @Override public String getLocalIp() { return mLocalIp; } + @Override public SipProfile getLocalProfile() { return mLocalProfile; } + @Override public SipProfile getPeerProfile() { return mPeerProfile; } + @Override public String getCallId() { return SipHelper.getCallId(getTransaction()); } @@ -649,10 +665,12 @@ class SipSessionGroup implements SipListener { return null; } + @Override public int getState() { return mState; } + @Override public void setListener(ISipSessionListener listener) { mProxy.setListener((listener instanceof SipSessionListenerProxy) ? ((SipSessionListenerProxy) listener).getListener() @@ -662,11 +680,12 @@ class SipSessionGroup implements SipListener { // process the command in a new thread private void doCommandAsync(final EventObject command) { new Thread(new Runnable() { + @Override public void run() { try { processCommand(command); } catch (Throwable e) { - Log.w(TAG, "command error: " + command + ": " + loge("command error: " + command + ": " + mLocalProfile.getUriString(), getRootCause(e)); onError(e); @@ -675,12 +694,14 @@ class SipSessionGroup implements SipListener { }, "SipSessionAsyncCmdThread").start(); } + @Override public void makeCall(SipProfile peerProfile, String sessionDescription, int timeout) { doCommandAsync(new MakeCallCommand(peerProfile, sessionDescription, timeout)); } + @Override public void answerCall(String sessionDescription, int timeout) { synchronized (SipSessionGroup.this) { if (mPeerProfile == null) return; @@ -689,10 +710,12 @@ class SipSessionGroup implements SipListener { } } + @Override public void endCall() { doCommandAsync(END_CALL); } + @Override public void changeCall(String sessionDescription, int timeout) { synchronized (SipSessionGroup.this) { if (mPeerProfile == null) return; @@ -701,16 +724,18 @@ class SipSessionGroup implements SipListener { } } + @Override public void register(int duration) { doCommandAsync(new RegisterCommand(duration)); } + @Override public void unregister() { doCommandAsync(DEREGISTER); } private void processCommand(EventObject command) throws SipException { - if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); + if (isLoggable(command)) log("process cmd: " + command); if (!process(command)) { onError(SipErrorCode.IN_PROGRESS, "cannot initiate a new transaction to execute: " @@ -723,6 +748,7 @@ class SipSessionGroup implements SipListener { return String.valueOf((long) (Math.random() * 0x100000000L)); } + @Override public String toString() { try { String s = super.toString(); @@ -734,15 +760,15 @@ class SipSessionGroup implements SipListener { } public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": " + if (isLoggable(this, evt)) log(" ~~~~~ " + this + ": " + SipSession.State.toString(mState) + ": processing " - + log(evt)); + + logEvt(evt)); synchronized (SipSessionGroup.this) { if (isClosed()) return false; - if (mKeepAliveProcess != null) { + if (mSipKeepAlive != null) { // event consumed by keepalive process - if (mKeepAliveProcess.process(evt)) return true; + if (mSipKeepAlive.process(evt)) return true; } Dialog dialog = null; @@ -824,7 +850,7 @@ class SipSessionGroup implements SipListener { if (mDialog == event.getDialog()) { onError(new SipException("dialog terminated")); } else { - Log.d(TAG, "not the current dialog; current=" + mDialog + if (SSI_DBG) log("not the current dialog; current=" + mDialog + ", terminated=" + event.getDialog()); } } @@ -838,11 +864,11 @@ class SipSessionGroup implements SipListener { : event.getClientTransaction(); if ((current != target) && (mState != SipSession.State.PINGING)) { - Log.d(TAG, "not the current transaction; current=" + if (SSI_DBG) log("not the current transaction; current=" + toString(current) + ", target=" + toString(target)); return false; } else if (current != null) { - Log.d(TAG, "transaction terminated: " + toString(current)); + if (SSI_DBG) log("transaction terminated: " + toString(current)); return true; } else { // no transaction; shouldn't be here; ignored @@ -865,17 +891,17 @@ class SipSessionGroup implements SipListener { switch (mState) { case SipSession.State.IN_CALL: case SipSession.State.READY_TO_CALL: - Log.d(TAG, "Transaction terminated; do nothing"); + if (SSI_DBG) log("Transaction terminated; do nothing"); break; default: - Log.d(TAG, "Transaction terminated early: " + this); + if (SSI_DBG) log("Transaction terminated early: " + this); onError(SipErrorCode.TRANSACTION_TERMINTED, "transaction terminated"); } } private void processTimeout(TimeoutEvent event) { - Log.d(TAG, "processing Timeout..."); + if (SSI_DBG) log("processing Timeout..."); switch (mState) { case SipSession.State.REGISTERING: case SipSession.State.DEREGISTERING: @@ -890,7 +916,7 @@ class SipSessionGroup implements SipListener { break; default: - Log.d(TAG, " do nothing"); + if (SSI_DBG) log(" do nothing"); break; } } @@ -912,8 +938,8 @@ class SipSessionGroup implements SipListener { if (expires != null && time < expires.getExpires()) { time = expires.getExpires(); } - if (DEBUG) { - Log.v(TAG, "Expiry time = " + time); + if (SSI_DBG) { + log("Expiry time = " + time); } return time; } @@ -960,7 +986,7 @@ class SipSessionGroup implements SipListener { mDialog = mClientTransaction.getDialog(); mAuthenticationRetryCount++; if (isLoggable(this, event)) { - Log.d(TAG, " authentication retry count=" + if (SSI_DBG) log(" authentication retry count=" + mAuthenticationRetryCount); } return true; @@ -984,19 +1010,23 @@ class SipSessionGroup implements SipListener { private AccountManager getAccountManager() { return new AccountManager() { + @Override public UserCredentials getCredentials(ClientTransaction challengedTransaction, String realm) { return new UserCredentials() { + @Override public String getUserName() { String username = mLocalProfile.getAuthUserName(); return (!TextUtils.isEmpty(username) ? username : mLocalProfile.getUserName()); } + @Override public String getPassword() { return mPassword; } + @Override public String getSipDomain() { return mLocalProfile.getSipDomain(); } @@ -1097,8 +1127,7 @@ class SipSessionGroup implements SipListener { return false; } - private boolean incomingCallToInCall(EventObject evt) - throws SipException { + private boolean incomingCallToInCall(EventObject evt) { // expect ACK, CANCEL request if (isRequestEvent(Request.ACK, evt)) { String sdp = extractContent(((RequestEvent) evt).getRequest()); @@ -1154,8 +1183,7 @@ class SipSessionGroup implements SipListener { } return true; case Response.REQUEST_PENDING: - // TODO: - // rfc3261#section-14.1; re-schedule invite + // TODO: rfc3261#section-14.1; re-schedule invite return true; default: if (mReferSession != null) { @@ -1342,17 +1370,17 @@ class SipSessionGroup implements SipListener { } private void enableKeepAlive() { - if (mKeepAliveSession != null) { - mKeepAliveSession.stopKeepAliveProcess(); + if (mSipSessionImpl != null) { + mSipSessionImpl.stopKeepAliveProcess(); } else { - mKeepAliveSession = duplicate(); + mSipSessionImpl = duplicate(); } try { - mKeepAliveSession.startKeepAliveProcess( + mSipSessionImpl.startKeepAliveProcess( INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null); } catch (SipException e) { - Log.w(TAG, "keepalive cannot be enabled; ignored", e); - mKeepAliveSession.stopKeepAliveProcess(); + loge("keepalive cannot be enabled; ignored", e); + mSipSessionImpl.stopKeepAliveProcess(); } } @@ -1454,12 +1482,6 @@ class SipSessionGroup implements SipListener { mProxy.onRegistrationFailed(this, errorCode, message); } - private void onRegistrationFailed(Throwable exception) { - exception = getRootCause(exception); - onRegistrationFailed(getErrorCode(exception), - exception.toString()); - } - private void onRegistrationFailed(Response response) { int statusCode = response.getStatusCode(); onRegistrationFailed(getErrorCode(statusCode), @@ -1480,28 +1502,30 @@ class SipSessionGroup implements SipListener { public void startKeepAliveProcess(int interval, SipProfile peerProfile, KeepAliveProcessCallback callback) throws SipException { synchronized (SipSessionGroup.this) { - if (mKeepAliveProcess != null) { + if (mSipKeepAlive != null) { throw new SipException("Cannot create more than one " + "keepalive process in a SipSession"); } mPeerProfile = peerProfile; - mKeepAliveProcess = new KeepAliveProcess(); - mProxy.setListener(mKeepAliveProcess); - mKeepAliveProcess.start(interval, callback); + mSipKeepAlive = new SipKeepAlive(); + mProxy.setListener(mSipKeepAlive); + mSipKeepAlive.start(interval, callback); } } public void stopKeepAliveProcess() { synchronized (SipSessionGroup.this) { - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; + if (mSipKeepAlive != null) { + mSipKeepAlive.stop(); + mSipKeepAlive = null; } } } - class KeepAliveProcess extends SipSessionAdapter implements Runnable { - private static final String TAG = "SipKeepAlive"; + class SipKeepAlive extends SipSessionAdapter implements Runnable { + private static final String SKA_TAG = "SipKeepAlive"; + private static final boolean SKA_DBG = true; + private boolean mRunning = false; private KeepAliveProcessCallback mCallback; @@ -1516,8 +1540,8 @@ class SipSessionGroup implements SipListener { mInterval = interval; mCallback = new KeepAliveProcessCallbackProxy(callback); mWakeupTimer.set(interval * 1000, this); - if (DEBUG) { - Log.d(TAG, "start keepalive:" + if (SKA_DBG) { + log("start keepalive:" + mLocalProfile.getUriString()); } @@ -1526,7 +1550,7 @@ class SipSessionGroup implements SipListener { } // return true if the event is consumed - boolean process(EventObject evt) throws SipException { + boolean process(EventObject evt) { if (mRunning && (mState == SipSession.State.PINGING)) { if (evt instanceof ResponseEvent) { if (parseOptionsResult(evt)) { @@ -1560,18 +1584,18 @@ class SipSessionGroup implements SipListener { synchronized (SipSessionGroup.this) { if (!mRunning) return; - if (DEBUG_PING) { + if (DBG_PING) { String peerUri = (mPeerProfile == null) ? "null" : mPeerProfile.getUriString(); - Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() + log("keepalive: " + mLocalProfile.getUriString() + " --> " + peerUri + ", interval=" + mInterval); } try { sendKeepAlive(); } catch (Throwable t) { - if (DEBUG) { - Log.w(TAG, "keepalive error: " + if (SKA_DBG) { + loge("keepalive error: " + mLocalProfile.getUriString(), getRootCause(t)); } // It's possible that the keepalive process is being stopped @@ -1584,8 +1608,8 @@ class SipSessionGroup implements SipListener { void stop() { synchronized (SipSessionGroup.this) { - if (DEBUG) { - Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString() + if (SKA_DBG) { + log("stop keepalive:" + mLocalProfile.getUriString() + ",RPort=" + mRPort); } mRunning = false; @@ -1594,7 +1618,7 @@ class SipSessionGroup implements SipListener { } } - private void sendKeepAlive() throws SipException, InterruptedException { + private void sendKeepAlive() throws SipException { synchronized (SipSessionGroup.this) { mState = SipSession.State.PINGING; mClientTransaction = mSipHelper.sendOptions( @@ -1615,14 +1639,14 @@ class SipSessionGroup implements SipListener { if (mRPort == 0) mRPort = rPort; if (mRPort != rPort) { mPortChanged = true; - if (DEBUG) Log.d(TAG, String.format( + if (SKA_DBG) log(String.format( "rport is changed: %d <> %d", mRPort, rPort)); mRPort = rPort; } else { - if (DEBUG) Log.d(TAG, "rport is the same: " + rPort); + if (SKA_DBG) log("rport is the same: " + rPort); } } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); + if (SKA_DBG) log("peer did not respond rport"); } return true; } @@ -1634,6 +1658,14 @@ class SipSessionGroup implements SipListener { SIPHeaderNames.VIA)); return (viaHeader == null) ? -1 : viaHeader.getRPort(); } + + private void log(String s) { + Rlog.d(SKA_TAG, s); + } + } + + private void log(String s) { + Rlog.d(SSI_TAG, s); } } @@ -1670,22 +1702,6 @@ class SipSessionGroup implements SipListener { return false; } - /** - * @return true if the event is a response event and the response code and - * CSeqHeader method match the given arguments; false otherwise - */ - private static boolean expectResponse( - int responseCode, String expectedMethod, EventObject evt) { - if (evt instanceof ResponseEvent) { - ResponseEvent event = (ResponseEvent) evt; - Response response = event.getResponse(); - if (response.getStatusCode() == responseCode) { - return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); - } - } - return false; - } - private static SipProfile createPeerProfile(HeaderAddress header) throws SipException { try { @@ -1710,10 +1726,10 @@ class SipSessionGroup implements SipListener { if (s != null) { switch (s.mState) { case SipSession.State.PINGING: - return DEBUG_PING; + return DBG_PING; } } - return DEBUG; + return DBG; } private static boolean isLoggable(EventObject evt) { @@ -1727,19 +1743,19 @@ class SipSessionGroup implements SipListener { if (evt instanceof ResponseEvent) { Response response = ((ResponseEvent) evt).getResponse(); if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { - return DEBUG_PING; + return DBG_PING; } - return DEBUG; + return DBG; } else if (evt instanceof RequestEvent) { if (isRequestEvent(Request.OPTIONS, evt)) { - return DEBUG_PING; + return DBG_PING; } - return DEBUG; + return DBG; } return false; } - private static String log(EventObject evt) { + private static String logEvt(EventObject evt) { if (evt instanceof RequestEvent) { return ((RequestEvent) evt).getRequest().toString(); } else if (evt instanceof ResponseEvent) { @@ -1766,11 +1782,6 @@ class SipSessionGroup implements SipListener { private String mSessionDescription; private int mTimeout; // in seconds - public MakeCallCommand(SipProfile peerProfile, - String sessionDescription) { - this(peerProfile, sessionDescription, -1); - } - public MakeCallCommand(SipProfile peerProfile, String sessionDescription, int timeout) { super(peerProfile); @@ -1793,6 +1804,7 @@ class SipSessionGroup implements SipListener { /** Class to help safely run KeepAliveProcessCallback in a different thread. */ static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback { + private static final String KAPCP_TAG = "KeepAliveProcessCallbackProxy"; private KeepAliveProcessCallback mCallback; KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) { @@ -1806,30 +1818,46 @@ class SipSessionGroup implements SipListener { new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start(); } + @Override public void onResponse(final boolean portChanged) { if (mCallback == null) return; proxy(new Runnable() { + @Override public void run() { try { mCallback.onResponse(portChanged); } catch (Throwable t) { - Log.w(TAG, "onResponse", t); + loge("onResponse", t); } } }); } + @Override public void onError(final int errorCode, final String description) { if (mCallback == null) return; proxy(new Runnable() { + @Override public void run() { try { mCallback.onError(errorCode, description); } catch (Throwable t) { - Log.w(TAG, "onError", t); + loge("onError", t); } } }); } + + private void loge(String s, Throwable t) { + Rlog.e(KAPCP_TAG, s, t); + } + } + + private void log(String s) { + Rlog.d(TAG, s); + } + + private void loge(String s, Throwable t) { + Rlog.e(TAG, s, t); } } diff --git a/java/com/android/server/sip/SipSessionListenerProxy.java b/java/com/android/server/sip/SipSessionListenerProxy.java index 8655a3a..7a4ae8d 100644 --- a/java/com/android/server/sip/SipSessionListenerProxy.java +++ b/java/com/android/server/sip/SipSessionListenerProxy.java @@ -20,11 +20,11 @@ import android.net.sip.ISipSession; import android.net.sip.ISipSessionListener; import android.net.sip.SipProfile; import android.os.DeadObjectException; -import android.util.Log; +import android.telephony.Rlog; /** Class to help safely run a callback in a different thread. */ class SipSessionListenerProxy extends ISipSessionListener.Stub { - private static final String TAG = "SipSession"; + private static final String TAG = "SipSessionListnerProxy"; private ISipSessionListener mListener; @@ -43,9 +43,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { new Thread(runnable, "SipSessionCallbackThread").start(); } + @Override public void onCalling(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCalling(session); @@ -56,10 +58,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRinging(final ISipSession session, final SipProfile caller, final String sessionDescription) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRinging(session, caller, sessionDescription); @@ -70,9 +74,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRingingBack(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRingingBack(session); @@ -83,10 +89,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onCallEstablished(final ISipSession session, final String sessionDescription) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCallEstablished(session, sessionDescription); @@ -97,9 +105,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onCallEnded(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCallEnded(session); @@ -110,10 +120,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onCallTransferring(final ISipSession newSession, final String sessionDescription) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCallTransferring(newSession, sessionDescription); @@ -124,9 +136,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onCallBusy(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCallBusy(session); @@ -137,10 +151,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onCallChangeFailed(final ISipSession session, final int errorCode, final String message) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onCallChangeFailed(session, errorCode, message); @@ -151,10 +167,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onError(final ISipSession session, final int errorCode, final String message) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onError(session, errorCode, message); @@ -165,9 +183,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRegistering(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRegistering(session); @@ -178,10 +198,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRegistrationDone(final ISipSession session, final int duration) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRegistrationDone(session, duration); @@ -192,10 +214,12 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRegistrationFailed(final ISipSession session, final int errorCode, final String message) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRegistrationFailed(session, errorCode, message); @@ -206,9 +230,11 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { }); } + @Override public void onRegistrationTimeout(final ISipSession session) { if (mListener == null) return; proxy(new Runnable() { + @Override public void run() { try { mListener.onRegistrationTimeout(session); @@ -225,7 +251,15 @@ class SipSessionListenerProxy extends ISipSessionListener.Stub { // This creates race but it's harmless. Just don't log the error // when it happens. } else if (mListener != null) { - Log.w(TAG, message, t); + loge(message, t); } } + + private void log(String s) { + Rlog.d(TAG, s); + } + + private void loge(String s, Throwable t) { + Rlog.e(TAG, s, t); + } } diff --git a/java/com/android/server/sip/SipWakeLock.java b/java/com/android/server/sip/SipWakeLock.java index 0c4d14c..b3fbb56 100644 --- a/java/com/android/server/sip/SipWakeLock.java +++ b/java/com/android/server/sip/SipWakeLock.java @@ -17,13 +17,13 @@ package com.android.server.sip; import android.os.PowerManager; -import android.util.Log; +import android.telephony.Rlog; import java.util.HashSet; class SipWakeLock { - private static final boolean DEBUG = false; private static final String TAG = "SipWakeLock"; + private static final boolean DBG = false; private PowerManager mPowerManager; private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mTimerWakeLock; @@ -34,7 +34,7 @@ class SipWakeLock { } synchronized void reset() { - if (DEBUG) Log.v(TAG, "reset count=" + mHolders.size()); + if (DBG) log("reset count=" + mHolders.size()); mHolders.clear(); release(null); } @@ -55,7 +55,7 @@ class SipWakeLock { PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock"); } if (!mWakeLock.isHeld()) mWakeLock.acquire(); - if (DEBUG) Log.v(TAG, "acquire count=" + mHolders.size()); + if (DBG) log("acquire count=" + mHolders.size()); } synchronized void release(Object holder) { @@ -64,6 +64,10 @@ class SipWakeLock { && mWakeLock.isHeld()) { mWakeLock.release(); } - if (DEBUG) Log.v(TAG, "release count=" + mHolders.size()); + if (DBG) log("release count=" + mHolders.size()); + } + + private void log(String s) { + Rlog.d(TAG, s); } } diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java index 00d47ac..3ba4331 100644 --- a/java/com/android/server/sip/SipWakeupTimer.java +++ b/java/com/android/server/sip/SipWakeupTimer.java @@ -23,31 +23,20 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.SystemClock; -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collection; +import android.telephony.Rlog; + import java.util.Comparator; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; -import java.util.Timer; -import java.util.TimerTask; import java.util.TreeSet; import java.util.concurrent.Executor; -import javax.sip.SipException; /** * Timer that can schedule events to occur even when the device is in sleep. */ class SipWakeupTimer extends BroadcastReceiver { - private static final String TAG = "_SIP.WkTimer_"; + private static final String TAG = "SipWakeupTimer"; + private static final boolean DBG = SipService.DBG && true; // STOPSHIP if true private static final String TRIGGER_TIME = "TriggerTime"; - private static final boolean DEBUG_TIMER = SipService.DEBUG && false; private Context mContext; private AlarmManager mAlarmManager; @@ -85,7 +74,7 @@ class SipWakeupTimer extends BroadcastReceiver { private boolean stopped() { if (mEventQueue == null) { - Log.w(TAG, "Timer stopped"); + if (DBG) log("Timer stopped"); return true; } else { return false; @@ -112,11 +101,11 @@ class SipWakeupTimer extends BroadcastReceiver { } TreeSet newQueue = new TreeSet( mEventQueue.comparator()); - newQueue.addAll((Collection) mEventQueue); + newQueue.addAll(mEventQueue); mEventQueue.clear(); mEventQueue = newQueue; - if (DEBUG_TIMER) { - Log.d(TAG, "queue re-calculated"); + if (DBG) { + log("queue re-calculated"); printQueue(); } } @@ -172,8 +161,8 @@ class SipWakeupTimer extends BroadcastReceiver { } long triggerTime = event.mTriggerTime; - if (DEBUG_TIMER) { - Log.d(TAG, " add event " + event + " scheduled on " + if (DBG) { + log("set: add event " + event + " scheduled on " + showTime(triggerTime) + " at " + showTime(now) + ", #events=" + mEventQueue.size()); printQueue(); @@ -187,7 +176,7 @@ class SipWakeupTimer extends BroadcastReceiver { */ public synchronized void cancel(Runnable callback) { if (stopped() || mEventQueue.isEmpty()) return; - if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); + if (DBG) log("cancel:" + callback); MyEvent firstEvent = mEventQueue.first(); for (Iterator iter = mEventQueue.iterator(); @@ -195,7 +184,7 @@ class SipWakeupTimer extends BroadcastReceiver { MyEvent event = iter.next(); if (event.mCallback == callback) { iter.remove(); - if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); + if (DBG) log(" cancel found:" + event); } } if (mEventQueue.isEmpty()) { @@ -209,8 +198,8 @@ class SipWakeupTimer extends BroadcastReceiver { recalculatePeriods(); scheduleNext(); } - if (DEBUG_TIMER) { - Log.d(TAG, "after cancel:"); + if (DBG) { + log("cancel: X"); printQueue(); } } @@ -242,33 +231,33 @@ class SipWakeupTimer extends BroadcastReceiver { long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); execute(triggerTime); } else { - Log.d(TAG, "unrecognized intent: " + intent); + log("onReceive: unrecognized intent: " + intent); } } private void printQueue() { int count = 0; for (MyEvent event : mEventQueue) { - Log.d(TAG, " " + event + ": scheduled at " + log(" " + event + ": scheduled at " + showTime(event.mTriggerTime) + ": last at " + showTime(event.mLastTriggerTime)); if (++count >= 5) break; } if (mEventQueue.size() > count) { - Log.d(TAG, " ....."); + log(" ....."); } else if (count == 0) { - Log.d(TAG, " "); + log(" "); } } private void execute(long triggerTime) { - if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " + if (DBG) log("time's up, triggerTime = " + showTime(triggerTime) + ": " + mEventQueue.size()); if (stopped() || mEventQueue.isEmpty()) return; for (MyEvent event : mEventQueue) { if (event.mTriggerTime != triggerTime) continue; - if (DEBUG_TIMER) Log.d(TAG, "execute " + event); + if (DBG) log("execute " + event); event.mLastTriggerTime = triggerTime; event.mTriggerTime += event.mPeriod; @@ -276,8 +265,8 @@ class SipWakeupTimer extends BroadcastReceiver { // run the callback in the handler thread to prevent deadlock mExecutor.execute(event.mCallback); } - if (DEBUG_TIMER) { - Log.d(TAG, "after timeout execution"); + if (DBG) { + log("after timeout execution"); printQueue(); } scheduleNext(); @@ -327,6 +316,7 @@ class SipWakeupTimer extends BroadcastReceiver { // Sort the events by mMaxPeriod so that the first event can be used to // align events with larger periods private static class MyEventComparator implements Comparator { + @Override public int compare(MyEvent e1, MyEvent e2) { if (e1 == e2) return 0; int diff = e1.mMaxPeriod - e2.mMaxPeriod; @@ -334,8 +324,13 @@ class SipWakeupTimer extends BroadcastReceiver { return diff; } + @Override public boolean equals(Object that) { return (this == that); } } + + private void log(String s) { + Rlog.d(TAG, s); + } } -- cgit v1.2.3