diff options
39 files changed, 9 insertions, 11353 deletions
@@ -219,10 +219,7 @@ LOCAL_SRC_FILES += \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ - wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ - voip/java/android/net/sip/ISipSession.aidl \ - voip/java/android/net/sip/ISipSessionListener.aidl \ - voip/java/android/net/sip/ISipService.aidl + wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl # @@ -353,6 +350,8 @@ non_base_dirs := \ ../../external/apache-http/src/org/apache/http \ ../opt/telephony/src/java/android/telephony \ ../opt/telephony/src/java/android/telephony/gsm \ + ../opt/net/voip/src/java/android/net/rtp \ + ../opt/net/voip/src/java/android/net/sip # These are relative to frameworks/base dirs_to_check_apis := \ @@ -405,6 +404,7 @@ framework_docs_LOCAL_JAVA_LIBRARIES := \ framework \ mms-common \ telephony-common \ + voip-common \ framework_docs_LOCAL_MODULE_CLASS := JAVA_LIBRARIES framework_docs_LOCAL_DROIDDOC_HTML_DIR := docs/html diff --git a/CleanSpec.mk b/CleanSpec.mk index 06f7c54..2c7d16f 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -149,6 +149,11 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationController.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.java) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/IDisplayMagnificationMediator.P) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/voip) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/librtp_jni_intermediates) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/librtp_jni.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/librtp_jni.so) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/librtp_jni.so) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java deleted file mode 100644 index 85255c8..0000000 --- a/voip/java/android/net/rtp/AudioCodec.java +++ /dev/null @@ -1,146 +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.rtp; - -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. - * - * <p>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.</p> - * <pre> - * AudioCodec codec = AudioCodec.getCodec(100, "AMR/8000", "mode-set=1"); - * </pre> - * - * @see AudioStream - */ -public class AudioCodec { - /** - * 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); - - /** - * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or - * simply FR. - */ - 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); - - private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA}; - - private AudioCodec(int type, String rtpmap, String fmtp) { - this.type = type; - this.rtpmap = rtpmap; - this.fmtp = fmtp; - } - - /** - * Returns system supported audio codecs. - */ - public static AudioCodec[] getCodecs() { - return Arrays.copyOf(sCodecs, sCodecs.length); - } - - /** - * Creates an AudioCodec according to the given configuration. - * - * @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 getCodec(int type, String rtpmap, String fmtp) { - if (type < 0 || type > 127) { - return null; - } - - 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; - } - } - } - - 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/voip/java/android/net/rtp/AudioGroup.java b/voip/java/android/net/rtp/AudioGroup.java deleted file mode 100644 index 8faeb88..0000000 --- a/voip/java/android/net/rtp/AudioGroup.java +++ /dev/null @@ -1,206 +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.rtp; - -import android.media.AudioManager; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * 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. - * - * <p>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 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.</p> - * - * <p>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 - * 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 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.</p> - * - * <p class="note">Using this class requires - * {@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.</p> - * - * @see AudioStream - */ -public class AudioGroup { - /** - * This mode is similar to {@link #MODE_NORMAL} except the speaker and - * 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 - * disabled. - */ - 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; - - /** - * 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 static final int MODE_LAST = 3; - - private final Map<AudioStream, Integer> mStreams; - private int mMode = MODE_ON_HOLD; - - private int mNative; - static { - System.loadLibrary("rtp_jni"); - } - - /** - * Creates an empty AudioGroup. - */ - public AudioGroup() { - mStreams = new HashMap<AudioStream, Integer>(); - } - - /** - * 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. - */ - 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 void setMode(int mode) { - if (mode < 0 || mode > MODE_LAST) { - throw new IllegalArgumentException("Invalid mode"); - } - synchronized (this) { - nativeSetMode(mode); - mMode = mode; - } - } - - private native void nativeSetMode(int mode); - - // Package-private method used by AudioStream.join(). - synchronized void add(AudioStream stream) { - if (!mStreams.containsKey(stream)) { - try { - AudioCodec codec = stream.getCodec(); - 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(), - stream.getRemotePort(), codecSpec, stream.getDtmfType()); - mStreams.put(stream, id); - } catch (NullPointerException e) { - throw new IllegalStateException(e); - } - } - } - - 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 id = mStreams.remove(stream); - if (id != null) { - nativeRemove(id); - } - } - - private native void nativeRemove(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 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 void clear() { - for (AudioStream stream : getStreams()) { - stream.join(null); - } - } - - @Override - protected void finalize() throws Throwable { - nativeRemove(0); - super.finalize(); - } -} diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java deleted file mode 100644 index 5cd1abc..0000000 --- a/voip/java/android/net/rtp/AudioStream.java +++ /dev/null @@ -1,167 +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.rtp; - -import java.net.InetAddress; -import java.net.SocketException; - -/** - * 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, developers - * should be aware of the limitations described in {@link AudioGroup}. - * - * <p>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.</p> - * - * <p class="note">Using this class requires - * {@link android.Manifest.permission#INTERNET} permission.</p> - * - * @see RtpStream - * @see AudioGroup - */ -public class AudioStream extends RtpStream { - private AudioCodec mCodec; - 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 has already joined an - * {@link AudioGroup}. - */ - @Override - public final boolean isBusy() { - return mGroup != null; - } - - /** - * Returns the joined {@link AudioGroup}. - */ - public AudioGroup getGroup() { - 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) { - synchronized (this) { - if (mGroup == group) { - return; - } - if (mGroup != null) { - mGroup.remove(this); - mGroup = null; - } - if (group != null) { - group.add(this); - mGroup = group; - } - } - } - - /** - * Returns the {@link AudioCodec}, or {@code null} if it is not set. - * - * @see #setCodec(AudioCodec) - */ - public AudioCodec getCodec() { - return mCodec; - } - - /** - * Sets the {@link AudioCodec}. - * - * @param codec The AudioCodec to be used. - * @throws IllegalArgumentException if its type is used by DTMF. - * @throws IllegalStateException if the stream is busy. - */ - public void setCodec(AudioCodec codec) { - if (isBusy()) { - throw new IllegalStateException("Busy"); - } - if (codec.type == mDtmfType) { - throw new IllegalArgumentException("The type is used by DTMF"); - } - 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 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. - * @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 (mCodec != null && type == mCodec.type) { - throw new IllegalArgumentException("The type is used by codec"); - } - } - mDtmfType = type; - } -} diff --git a/voip/java/android/net/rtp/RtpStream.java b/voip/java/android/net/rtp/RtpStream.java deleted file mode 100644 index b9d75cd..0000000 --- a/voip/java/android/net/rtp/RtpStream.java +++ /dev/null @@ -1,195 +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.rtp; - -import java.net.InetAddress; -import java.net.Inet4Address; -import java.net.Inet6Address; -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). - * - * <p class="note">Using this class requires - * {@link android.Manifest.permission#INTERNET} permission.</p> - */ -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 static final int MODE_LAST = 2; - - private final InetAddress mLocalAddress; - private final int mLocalPort; - - private InetAddress mRemoteAddress; - private int mRemotePort = -1; - private int mMode = MODE_NORMAL; - - private int mSocket = -1; - 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. 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. - */ - 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 < 0 || mode > MODE_LAST) { - throw new IllegalArgumentException("Invalid mode"); - } - mMode = mode; - } - - /** - * 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. - * @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; - } - - int getSocket() { - return mSocket; - } - - /** - * Releases allocated resources. The stream becomes inoperable after calling - * this method. - * - * @throws IllegalStateException if the stream is busy. - * @see #isBusy() - */ - public void release() { - synchronized (this) { - if (isBusy()) { - throw new IllegalStateException("Busy"); - } - close(); - } - } - - private native void close(); - - @Override - protected void finalize() throws Throwable { - close(); - super.finalize(); - } -} diff --git a/voip/java/android/net/rtp/package.html b/voip/java/android/net/rtp/package.html deleted file mode 100644 index 4506b09..0000000 --- a/voip/java/android/net/rtp/package.html +++ /dev/null @@ -1,28 +0,0 @@ -<html> -<body> -<p>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.</p> - -<p>To support audio conferencing and similar usages, you need to instantiate two classes as -endpoints for the stream:</p> - -<ul> -<li>{@link android.net.rtp.AudioStream} specifies a remote endpoint and consists of network mapping -and a configured {@link android.net.rtp.AudioCodec}.</li> - -<li>{@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.</li> -</ul> - -<p>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}.</p> - -<p class="note"><strong>Note:</strong> 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.</p> -</body> -</html>
\ No newline at end of file diff --git a/voip/java/android/net/sip/ISipService.aidl b/voip/java/android/net/sip/ISipService.aidl deleted file mode 100644 index 3250bf9..0000000 --- a/voip/java/android/net/sip/ISipService.aidl +++ /dev/null @@ -1,43 +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.app.PendingIntent; -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, - in PendingIntent incomingCallPendingIntent, - 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/voip/java/android/net/sip/ISipSession.aidl b/voip/java/android/net/sip/ISipSession.aidl deleted file mode 100644 index 2d515db..0000000 --- a/voip/java/android/net/sip/ISipSession.aidl +++ /dev/null @@ -1,148 +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.net.sip.ISipSessionListener; -import android.net.sip.SipProfile; - -/** - * A SIP session that is associated with a SIP dialog or a transaction that is - * 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}. - * - * @return the session state - */ - int 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 - * @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, int timeout); - - /** - * 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 - */ - void answerCall(String sessionDescription, int timeout); - - /** - * 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 - * @param timeout the session will be timed out if the call is not - * established within {@code timeout} seconds - */ - void changeCall(String sessionDescription, int timeout); -} diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl deleted file mode 100644 index 690700c..0000000 --- a/voip/java/android/net/sip/ISipSessionListener.aidl +++ /dev/null @@ -1,133 +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.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, - String 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, - String 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 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. - * - * @param session the session object that carries out the transaction - * @param errorCode error code defined in {@link SipErrorCode} - * @param errorMessage error message - */ - void onError(in ISipSession 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 - */ - void onCallChangeFailed(in ISipSession session, int errorCode, - 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 errorCode error code defined in {@link SipErrorCode} - * @param errorMessage error message - */ - void onRegistrationFailed(in ISipSession session, int errorCode, - 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/voip/java/android/net/sip/SimpleSessionDescription.java b/voip/java/android/net/sip/SimpleSessionDescription.java deleted file mode 100644 index 9fcd21d..0000000 --- a/voip/java/android/net/sip/SimpleSessionDescription.java +++ /dev/null @@ -1,613 +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 java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; - -/** - * 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. - * - * <p>Here is an example code to create a session description.</p> - * <pre> - * 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", ""); - * </pre> - * <p>Invoking <code>description.encode()</code> will produce a result like the - * one below.</p> - * <pre> - * 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 - * </pre> - * @hide - */ -public class SimpleSessionDescription { - private final Fields mFields = new Fields("voscbtka"); - private final ArrayList<Media> mMedia = new ArrayList<Media>(); - - /** - * 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(Locale.US, "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<String> mFormats = new ArrayList<String>(); - - 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 {@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. - */ - 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<String> mLines = new ArrayList<String>(); - - 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); - } - } -} diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java deleted file mode 100644 index ea943e9..0000000 --- a/voip/java/android/net/sip/SipAudioCall.java +++ /dev/null @@ -1,1143 +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.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.telephony.Rlog; -import android.text.TextUtils; -import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * 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()}. - * - * <p class="note"><strong>Note:</strong> Using this class require the - * {@link android.Manifest.permission#INTERNET} and - * {@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.</p> - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using SIP, read the - * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a> - * developer guide.</p> - * </div> - */ -public class SipAudioCall { - 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 - 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"). - * <p>Many of these events are also received by {@link SipSession.Listener}.</p> - */ - public static class Listener { - /** - * Called when the call object is ready to make another call. - * The default implementation calls {@link #onChanged}. - * - * @param call the call object that is ready to make another 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 #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onCalling(SipAudioCall call) { - onChanged(call); - } - - /** - * Called when a new call comes in. - * 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 - */ - public void onRinging(SipAudioCall call, SipProfile caller) { - onChanged(call); - } - - /** - * Called when a RINGING response is received for the INVITE request - * sent. The default implementation calls {@link #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onRingingBack(SipAudioCall call) { - onChanged(call); - } - - /** - * Called when the session is established. - * The default implementation calls {@link #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onCallEstablished(SipAudioCall call) { - onChanged(call); - } - - /** - * Called when the session is terminated. - * The default implementation calls {@link #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onCallEnded(SipAudioCall call) { - onChanged(call); - } - - /** - * Called when the peer is busy during session initialization. - * The default implementation calls {@link #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onCallBusy(SipAudioCall call) { - onChanged(call); - } - - /** - * Called when the call is on hold. - * The default implementation calls {@link #onChanged}. - * - * @param call the call object that carries out the audio call - */ - public void onCallHeld(SipAudioCall call) { - onChanged(call); - } - - /** - * 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 - */ - 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 SipSession mTransferringSession; - - 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 WifiManager mWm; - private WifiManager.WifiLock mWifiHighPerfLock; - - private int mErrorCode = SipErrorCode.NO_ERROR; - private String mErrorMessage; - - /** - * Creates a call object with the local SIP profile. - * @param context the context for accessing system services such as - * ringtone, audio, WIFI etc - */ - public SipAudioCall(Context context, SipProfile localProfile) { - mContext = context; - mLocalProfile = localProfile; - mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - } - - /** - * Sets the listener to listen to the audio call events. The method calls - * {@link #setListener setListener(listener, false)}. - * - * @param listener to listen to the audio call events of this object - * @see #setListener(Listener, boolean) - */ - public void setListener(SipAudioCall.Listener listener) { - setListener(listener, false); - } - - /** - * 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 - */ - 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) { - loge("setListener()", t); - } - } - - /** - * Checks if the call is established. - * - * @return true if the call is established - */ - public boolean isInCall() { - synchronized (this) { - return mInCall; - } - } - - /** - * Checks if the call is on hold. - * - * @return true if the call is on hold - */ - public boolean isOnHold() { - synchronized (this) { - return mHold; - } - } - - /** - * Closes this object. This object is not usable after being closed. - */ - public void close() { - close(true); - } - - private synchronized void close(boolean closeRtp) { - if (closeRtp) stopCall(RELEASE_SOCKET); - - mInCall = false; - mHold = false; - mSessionId = System.currentTimeMillis(); - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - - if (mSipSession != null) { - mSipSession.setListener(null); - mSipSession = null; - } - } - - /** - * Gets the local SIP profile. - * - * @return the local SIP profile - */ - public SipProfile getLocalProfile() { - synchronized (this) { - return mLocalProfile; - } - } - - /** - * Gets the peer's SIP profile. - * - * @return the peer's SIP profile - */ - public SipProfile getPeerProfile() { - synchronized (this) { - 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 int getState() { - synchronized (this) { - 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 SipSession getSipSession() { - synchronized (this) { - return mSipSession; - } - } - - 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) { - loge("transferToNewSession():", t); - } - } - if (origin != null) origin.endCall(); - startAudio(); - } - - private SipSession.Listener createListener() { - return new SipSession.Listener() { - @Override - public void onCalling(SipSession session) { - if (DBG) log("onCalling: session=" + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onCalling(SipAudioCall.this); - } catch (Throwable t) { - loge("onCalling():", t); - } - } - } - - @Override - public void onRingingBack(SipSession session) { - if (DBG) log("onRingingBackk: " + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onRingingBack(SipAudioCall.this); - } catch (Throwable t) { - loge("onRingingBack():", t); - } - } - } - - @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( - 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) { - loge("onRinging():", e); - session.endCall(); - } - } - } - - @Override - public void onCallEstablished(SipSession session, - String sessionDescription) { - mPeerSd = sessionDescription; - if (DBG) log("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 { - if (mHold) { - listener.onCallHeld(SipAudioCall.this); - } else { - listener.onCallEstablished(SipAudioCall.this); - } - } catch (Throwable t) { - loge("onCallEstablished(): ", t); - } - } - } - - @Override - public void onCallEnded(SipSession session) { - if (DBG) log("onCallEnded: " + 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 { - listener.onCallEnded(SipAudioCall.this); - } catch (Throwable t) { - loge("onCallEnded(): ", t); - } - } - close(); - } - - @Override - public void onCallBusy(SipSession session) { - if (DBG) log("onCallBusy: " + session); - Listener listener = mListener; - if (listener != null) { - try { - listener.onCallBusy(SipAudioCall.this); - } catch (Throwable t) { - loge("onCallBusy(): ", t); - } - } - close(false); - } - - @Override - public void onCallChangeFailed(SipSession session, int errorCode, - String message) { - if (DBG) log("onCallChangedFailed: " + message); - mErrorCode = errorCode; - mErrorMessage = message; - Listener listener = mListener; - if (listener != null) { - try { - listener.onError(SipAudioCall.this, mErrorCode, - message); - } catch (Throwable t) { - loge("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 - } - - @Override - public void onCallTransferring(SipSession newSession, - String sessionDescription) { - if (DBG) log("onCallTransferring: mSipSession=" - + mSipSession + " newSession=" + newSession); - mTransferringSession = newSession; - try { - 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) { - loge("onCallTransferring()", e); - newSession.endCall(); - } - } - }; - } - - private void onError(int errorCode, String message) { - if (DBG) log("onError: " - + SipErrorCode.toString(errorCode) + ": " + message); - mErrorCode = errorCode; - mErrorMessage = message; - Listener listener = mListener; - if (listener != null) { - try { - listener.onError(this, errorCode, message); - } catch (Throwable t) { - loge("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 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; - if (DBG) log("attachCall(): " + mPeerSd); - try { - session.setListener(createListener()); - } catch (Throwable e) { - loge("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 {@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 - * @throws SipException if the SIP service fails to create a session for the - * 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 (DBG) log("makeCall: " + peerProfile + " session=" + sipSession + " timeout=" + timeout); - if (!SipManager.isVoipSupported(mContext)) { - throw new SipException("VOIP API is not supported"); - } - - synchronized (this) { - mSipSession = sipSession; - try { - mAudioStream = new AudioStream(InetAddress.getByName( - getLocalIp())); - sipSession.setListener(createListener()); - sipSession.makeCall(peerProfile, createOffer().encode(), - timeout); - } catch (IOException e) { - loge("makeCall:", e); - throw new SipException("makeCall()", e); - } - } - } - - /** - * Ends a call. - * @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; - - // 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 - * called. The attempt will be timed out if the call is not established - * within {@code timeout} seconds and - * {@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 - * @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); - mHold = true; - setAudioGroupMode(); - } - } - - /** - * Answers a call. The attempt will be timed out if the call is not - * established within {@code timeout} seconds and - * {@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 - * @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"); - } - try { - mAudioStream = new AudioStream(InetAddress.getByName( - getLocalIp())); - mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout); - } catch (IOException e) { - loge("answerCall:", e); - throw new SipException("answerCall()", e); - } - } - } - - /** - * 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 - * {@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 - * @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); - mHold = false; - setAudioGroupMode(); - } - } - - 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"); - if (DBG) log("createOffer: offer=" + offer); - return offer; - } - - private SimpleSessionDescription createAnswer(String offerSd) { - if (TextUtils.isEmpty(offerSd)) return createOffer(); - 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) { - 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( - "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) { - if (DBG) log("grabWifiHighPerfLock:"); - mWifiHighPerfLock = ((WifiManager) - mContext.getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOG_TAG); - mWifiHighPerfLock.acquire(); - } - } - - private void releaseWifiHighPerfLock() { - if (mWifiHighPerfLock != null) { - if (DBG) log("releaseWifiHighPerfLock:"); - mWifiHighPerfLock.release(); - mWifiHighPerfLock = null; - } - } - - private boolean isWifiOn() { - return (mWm.getConnectionInfo().getBSSID() == null) ? false : true; - } - - /** Toggles mute. */ - public void toggleMute() { - synchronized (this) { - mMuted = !mMuted; - setAudioGroupMode(); - } - } - - /** - * Checks if the call is muted. - * - * @return true if the call is muted - */ - public boolean isMuted() { - synchronized (this) { - return mMuted; - } - } - - /** - * Puts the device to speaker mode. - * <p class="note"><strong>Note:</strong> Requires the - * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} permission.</p> - * - * @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 <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, - * 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. - */ - public void sendDtmf(int code) { - sendDtmf(code, null); - } - - /** - * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2883</a>, - * 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. - * @param result the result message to send when done - */ - public void sendDtmf(int code, Message result) { - synchronized (this) { - AudioGroup audioGroup = getAudioGroup(); - if ((audioGroup != null) && (mSipSession != null) - && (SipSession.State.IN_CALL == getState())) { - if (DBG) log("sendDtmf: code=" + code + " result=" + result); - audioGroup.sendDtmf(code); - } - if (result != null) result.sendToTarget(); - } - } - - /** - * 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 - * @hide - */ - public AudioStream getAudioStream() { - synchronized (this) { - return mAudioStream; - } - } - - /** - * 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. 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 - * @see #getAudioStream - * @hide - */ - public AudioGroup getAudioGroup() { - synchronized (this) { - if (mAudioGroup != null) return mAudioGroup; - return ((mAudioStream == null) ? null : mAudioStream.getGroup()); - } - } - - /** - * 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. 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 - */ - public void setAudioGroup(AudioGroup group) { - synchronized (this) { - if (DBG) log("setAudioGroup: group=" + group); - if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) { - mAudioStream.join(group); - } - mAudioGroup = group; - } - } - - /** - * Starts the audio for the established call. This method should be called - * after {@link Listener#onCallEstablished} is called. - * <p class="note"><strong>Note:</strong> Requires the - * {@link android.Manifest.permission#RECORD_AUDIO}, - * {@link android.Manifest.permission#ACCESS_WIFI_STATE} and - * {@link android.Manifest.permission#WAKE_LOCK} permissions.</p> - */ - public void startAudio() { - try { - startAudioInternal(); - } catch (UnknownHostException e) { - onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage()); - } catch (Throwable e) { - onError(SipErrorCode.CLIENT_ERROR, e.getMessage()); - } - } - - private synchronized void startAudioInternal() throws UnknownHostException { - if (DBG) loge("startAudioInternal: mPeerSd=" + mPeerSd); - if (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(); - - // AudioGroup logic: - AudioGroup audioGroup = getAudioGroup(); - if (mHold) { - // 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); - } - setAudioGroupMode(); - } - - // 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); - } else if (mMuted) { - audioGroup.setMode(AudioGroup.MODE_MUTED); - } else if (isSpeakerOn()) { - audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION); - } else { - audioGroup.setMode(AudioGroup.MODE_NORMAL); - } - } - } - - private void stopCall(boolean releaseSocket) { - if (DBG) log("stopCall: releaseSocket=" + releaseSocket); - releaseWifiHighPerfLock(); - if (mAudioStream != null) { - mAudioStream.join(null); - - if (releaseSocket) { - mAudioStream.release(); - mAudioStream = null; - } - } - } - - private String getLocalIp() { - return mSipSession.getLocalIp(); - } - - private void throwSipException(Throwable throwable) throws SipException { - if (throwable instanceof SipException) { - throw (SipException) throwable; - } else { - throw new SipException("", throwable); - } - } - - 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/voip/java/android/net/sip/SipErrorCode.java b/voip/java/android/net/sip/SipErrorCode.java deleted file mode 100644 index 509728f..0000000 --- a/voip/java/android/net/sip/SipErrorCode.java +++ /dev/null @@ -1,101 +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 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. */ - public static final int NO_ERROR = 0; - - /** When some socket error occurs. */ - public static final int SOCKET_ERROR = -1; - - /** When server responds with an error. */ - public static final int SERVER_ERROR = -2; - - /** When transaction is terminated unexpectedly. */ - public static final int TRANSACTION_TERMINTED = -3; - - /** When some error occurs on the device, possibly due to a bug. */ - public static final int CLIENT_ERROR = -4; - - /** When the transaction gets timed out. */ - public static final int TIME_OUT = -5; - - /** When the remote URI is not valid. */ - public static final int INVALID_REMOTE_URI = -6; - - /** When the peer is not reachable. */ - public static final int PEER_NOT_REACHABLE = -7; - - /** When invalid credentials are provided. */ - public static final int INVALID_CREDENTIALS = -8; - - /** The client is in a transaction and cannot initiate a new one. */ - public static final int IN_PROGRESS = -9; - - /** 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; - - /** When the server is not reachable. */ - public static final int SERVER_UNREACHABLE = -12; - - 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"; - case CROSS_DOMAIN_AUTHENTICATION: - return "CROSS_DOMAIN_AUTHENTICATION"; - case SERVER_UNREACHABLE: - return "SERVER_UNREACHABLE"; - default: - return "UNKNOWN"; - } - } - - private SipErrorCode() { - } -} diff --git a/voip/java/android/net/sip/SipException.java b/voip/java/android/net/sip/SipException.java deleted file mode 100644 index 0339395..0000000 --- a/voip/java/android/net/sip/SipException.java +++ /dev/null @@ -1,37 +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; - -/** - * Indicates a general SIP-related exception. - */ -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/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java deleted file mode 100644 index a94232a..0000000 --- a/voip/java/android/net/sip/SipManager.java +++ /dev/null @@ -1,622 +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.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.telephony.Rlog; - -import java.text.ParseException; - -/** - * 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()}.</p> - * <p>The APIs in this class allows you to:</p> - * <ul> - * <li>Create a {@link SipSession} to get ready for making calls or listen for incoming calls. See - * {@link #createSipSession createSipSession()} and {@link #getSessionFor getSessionFor()}.</li> - * <li>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()}.</li> - * <li>Register and unregister with a SIP service provider, with - * {@link #register register()} and {@link #unregister unregister()}.</li> - * <li>Verify session connectivity, with {@link #isOpened isOpened()} and - * {@link #isRegistered isRegistered()}.</li> - * </ul> - * <p class="note"><strong>Note:</strong> 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.</p> - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using SIP, read the - * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a> - * developer guide.</p> - * </div> - */ -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; - - /** - * 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"; - - /** - * 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"; - - /** - * 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. - * @hide - */ - public static final String ACTION_SIP_INCOMING_CALL = - "com.android.phone.SIP_INCOMING_CALL"; - /** - * 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"; - /** - * 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"; - /** - * 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 TAG = "SipManager"; - - private ISipService mSipService; - private Context mContext; - - /** - * Creates a manager instance. Returns null if SIP API is not 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 newInstance(Context context) { - return (isApiSupported(context) ? new SipManager(context) : 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 API. - */ - public static boolean isVoipSupported(Context context) { - return context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context); - } - - /** - * 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(Context context) { - mContext = context; - createSipService(); - } - - private void createSipService() { - IBinder b = ServiceManager.getService(Context.SIP_SERVICE); - mSipService = ISipService.Stub.asInterface(b); - } - - /** - * 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)} - * instead. - * - * @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 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 - * 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 - * 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 - * @see #isIncomingCallIntent - * @see #getCallId - * @see #getOfferSessionDescription - */ - public void open(SipProfile localProfile, - PendingIntent incomingCallPendingIntent, - SipRegistrationListener listener) throws SipException { - if (incomingCallPendingIntent == null) { - throw new NullPointerException( - "incomingCallPendingIntent cannot be null"); - } - try { - mSipService.open3(localProfile, incomingCallPendingIntent, - createRelay(listener, localProfile.getUriString())); - } 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, PendingIntent, SipRegistrationListener)}). - * - * @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, localProfileUri)); - } 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 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 - * @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 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 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 { - try { - return mSipService.isRegistered(localProfileUri); - } catch (RemoteException e) { - throw new SipException("isRegistered()", e); - } - } - - /** - * 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 - * {@link SipAudioCall.Listener#onError onError(SipAudioCall, SipErrorCode.TIME_OUT, String)} - * will be called. - * - * @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. 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 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); - call.makeCall(peerProfile, s, timeout); - return call; - } - - /** - * 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 - * {@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 - * @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. 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 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(), - new SipProfile.Builder(peerProfileUri).build(), listener, - timeout); - } catch (ParseException e) { - throw new SipException("build SipProfile", e); - } - } - - /** - * Creates a {@link SipAudioCall} to take an incoming call. Before the call - * is returned, the listener will receive a - * {@link SipAudioCall.Listener#onRinging} - * callback. - * - * @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(Intent incomingCallIntent, - SipAudioCall.Listener listener) throws SipException { - if (incomingCallIntent == null) { - throw new SipException("Cannot retrieve session with null intent"); - } - - String callId = getCallId(incomingCallIntent); - if (callId == null) { - throw new SipException("Call ID missing in incoming call intent"); - } - - String offerSd = getOfferSessionDescription(incomingCallIntent); - if (offerSd == null) { - throw new SipException("Session description missing in incoming " - + "call intent"); - } - - try { - ISipSession session = mSipService.getPendingSession(callId); - 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); - 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); - String 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(EXTRA_CALL_ID); - } - - /** - * 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 String getOfferSessionDescription(Intent incomingCallIntent) { - return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD); - } - - /** - * Creates an incoming call broadcast intent. - * - * @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 callId, - String sessionDescription) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_CALL_ID, callId); - intent.putExtra(EXTRA_OFFER_SD, sessionDescription); - return intent; - } - - /** - * Manually registers the profile to the corresponding SIP provider for - * 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 {@link android.app.PendingIntent} when an incoming call is - * received. - * - * @param localProfile the SIP profile to register with - * @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 - */ - public void register(SipProfile localProfile, int expiryTime, - SipRegistrationListener listener) throws SipException { - 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); - } - } - - /** - * 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 - * @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, localProfile.getUriString())); - if (session == null) { - throw new SipException( - "SipService.createSession() returns null"); - } - session.unregister(); - } catch (RemoteException e) { - throw new SipException("unregister()", e); - } - } - - /** - * 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 - * returns null. - * - * @param incomingCallIntent the incoming call broadcast intent - * @return the session object that handles the incoming call - */ - public SipSession getSessionFor(Intent incomingCallIntent) - throws SipException { - try { - String callId = getCallId(incomingCallIntent); - ISipSession s = mSipService.getPendingSession(callId); - return ((s == null) ? null : new SipSession(s)); - } catch (RemoteException e) { - throw new SipException("getSessionFor()", e); - } - } - - private static ISipSessionListener createRelay( - SipRegistrationListener listener, String uri) { - return ((listener == null) ? null : new ListenerRelay(listener, uri)); - } - - /** - * 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 - * @param listener to listen to SIP session events - */ - public SipSession createSipSession(SipProfile localProfile, - 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); - } - } - - /** - * 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 new SipProfile[0]; - } - } - - private static class ListenerRelay extends SipSessionAdapter { - private SipRegistrationListener mListener; - private String mUri; - - // listener must not be null - public ListenerRelay(SipRegistrationListener listener, String uri) { - mListener = listener; - mUri = uri; - } - - private String getUri(ISipSession session) { - try { - return ((session == null) - ? mUri - : session.getLocalProfile().getUriString()); - } catch (Throwable e) { - // SipService died? SIP stack died? - Rlog.e(TAG, "getUri(): ", e); - return null; - } - } - - @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, int errorCode, - String message) { - mListener.onRegistrationFailed(getUri(session), errorCode, message); - } - - @Override - public void onRegistrationTimeout(ISipSession session) { - mListener.onRegistrationFailed(getUri(session), - SipErrorCode.TIME_OUT, "registration timed out"); - } - } -} diff --git a/voip/java/android/net/sip/SipProfile.aidl b/voip/java/android/net/sip/SipProfile.aidl deleted file mode 100644 index 3b6f68f..0000000 --- a/voip/java/android/net/sip/SipProfile.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 SipProfile; diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java deleted file mode 100644 index 0ef754c..0000000 --- a/voip/java/android/net/sip/SipProfile.java +++ /dev/null @@ -1,502 +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; -import android.text.TextUtils; - -import java.io.ObjectStreamException; -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; - -/** - * Defines a SIP profile, including a SIP account, domain and server information. - * <p>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}.</p> - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using SIP, read the - * <a href="{@docRoot}guide/topics/network/sip.html">Session Initiation Protocol</a> - * developer guide.</p> - * </div> - */ -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 = UDP; - private String mProfileName; - private String mAuthUserName; - private int mPort = DEFAULT_PORT; - private boolean mSendKeepAlive = false; - private boolean mAutoRegistration = true; - private transient int mCallingUid = 0; - - public static final Parcelable.Creator<SipProfile> CREATOR = - new Parcelable.Creator<SipProfile>() { - public SipProfile createFromParcel(Parcel in) { - return new SipProfile(in); - } - - public SipProfile[] newArray(int size) { - return new SipProfile[size]; - } - }; - - /** - * Helper class for creating 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); - } - } - - /** - * 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(); - mProfile.mPort = profile.getPort(); - } - - /** - * Constructor. - * - * @param uriString the URI string as "sip:<user_name>@<domain>" - * @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} 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 username used for authentication. - * - * @param name authentication username of the profile - * @return this builder object - */ - public Builder setAuthUserName(String name) { - mProfile.mAuthUserName = name; - return this; - } - - /** - * 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 IllegalArgumentException if the port number is out of range - */ - public Builder setPort(int port) throws IllegalArgumentException { - if ((port > 65535) || (port < 1000)) { - throw new IllegalArgumentException("incorrect port arugment: " + port); - } - mProfile.mPort = 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 IllegalArgumentException if the protocol is not recognized - */ - public Builder setProtocol(String protocol) - throws IllegalArgumentException { - if (protocol == null) { - throw new NullPointerException("protocol cannot be null"); - } - protocol = protocol.toUpperCase(); - if (!protocol.equals(UDP) && !protocol.equals(TCP)) { - throw new IllegalArgumentException( - "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 { - 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); - } - 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; - mCallingUid = in.readInt(); - mPort = in.readInt(); - mAuthUserName = in.readString(); - } - - @Override - 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); - out.writeInt(mCallingUid); - out.writeInt(mPort); - out.writeString(mAuthUserName); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Gets the SIP URI of this profile. - * - * @return the SIP URI of this profile - * @hide - */ - 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() { - // 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(); - } - - /** - * Gets the SIP address of this profile. - * - * @return the SIP address of this profile - * @hide - */ - 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 username for authentication. If it is null, then the username - * is used in authentication instead. - * - * @return the authentication username - * @see #getUserName - */ - public String getAuthUserName() { - return mAuthUserName; - } - - /** - * 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() { - return mPort; - } - - /** - * 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; - } - - /** - * 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; - } - - private Object readResolve() throws ObjectStreamException { - // For compatibility. - if (mPort == 0) mPort = DEFAULT_PORT; - return this; - } -} diff --git a/voip/java/android/net/sip/SipRegistrationListener.java b/voip/java/android/net/sip/SipRegistrationListener.java deleted file mode 100644 index 9968cc7..0000000 --- a/voip/java/android/net/sip/SipRegistrationListener.java +++ /dev/null @@ -1,48 +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; - -/** - * Listener for SIP registration events. - */ -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 the registration succeeded. - * - * @param localProfileUri the URI string of the SIP profile to register with - * @param expiryTime duration in seconds before the registration expires - */ - void onRegistrationDone(String localProfileUri, long expiryTime); - - /** - * Called when the registration failed. - * - * @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, int errorCode, - String errorMessage); -} diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java deleted file mode 100644 index edbc66f..0000000 --- a/voip/java/android/net/sip/SipSession.java +++ /dev/null @@ -1,574 +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.RemoteException; -import android.telephony.Rlog; - -/** - * Represents a SIP session that is associated with a SIP dialog or a standalone - * transaction not within a dialog. - * <p>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).</p> - */ -public final class SipSession { - private static final String TAG = "SipSession"; - - /** - * 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. */ - 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; - - /** When ending a call. @hide */ - public static final int ENDING_CALL = 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 PINGING: - return "PINGING"; - default: - return "NOT_DEFINED"; - } - } - - private State() { - } - } - - /** - * 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"). - * <p>Many of these events are also received by {@link SipAudioCall.Listener}.</p> - */ - 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 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. - * - * @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) { - loge("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) { - loge("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) { - loge("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) { - loge("getPeerProfile:", e); - return null; - } - } - - /** - * Gets the session state. The value returned must be one of the states in - * {@link State}. - * - * @return the session state - */ - public int getState() { - try { - return mSession.getState(); - } catch (RemoteException e) { - loge("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) { - loge("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) { - loge("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 State#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) { - loge("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 State#READY_TO_CALL}. - * - * @see Listener - */ - public void unregister() { - try { - mSession.unregister(); - } catch (RemoteException e) { - loge("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 State#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) { - loge("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 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 - * 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) { - loge("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 State#IN_CALL}, - * {@link State#INCOMING_CALL}, - * {@link State#OUTGOING_CALL} or - * {@link State#OUTGOING_CALL_RING_BACK}. - */ - public void endCall() { - try { - mSession.endCall(); - } catch (RemoteException e) { - loge("endCall:", e); - } - } - - /** - * Changes the session description during a call. The method is only valid - * 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 - * 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) { - loge("changeCall:", e); - } - } - - ISipSession getRealSession() { - return mSession; - } - - 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) { - mListener.onRinging(SipSession.this, caller, - sessionDescription); - } - } - - @Override - public void onRingingBack(ISipSession session) { - if (mListener != null) { - mListener.onRingingBack(SipSession.this); - } - } - - @Override - public void onCallEstablished(ISipSession session, - String sessionDescription) { - if (mListener != null) { - mListener.onCallEstablished(SipSession.this, - sessionDescription); - } - } - - @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) { - mListener.onCallTransferring( - new SipSession(session, SipSession.this.mListener), - sessionDescription); - - } - } - - @Override - public void onCallChangeFailed(ISipSession session, int errorCode, - String message) { - if (mListener != null) { - mListener.onCallChangeFailed(SipSession.this, errorCode, - message); - } - } - - @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) { - mListener.onRegistrationFailed(SipSession.this, errorCode, - message); - } - } - - @Override - public void onRegistrationTimeout(ISipSession session) { - if (mListener != null) { - mListener.onRegistrationTimeout(SipSession.this); - } - } - }; - } - - private void loge(String s, Throwable t) { - Rlog.e(TAG, s, t); - } -} diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java deleted file mode 100644 index f538983..0000000 --- a/voip/java/android/net/sip/SipSessionAdapter.java +++ /dev/null @@ -1,68 +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; - -/** - * 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, - String sessionDescription) { - } - - public void onRingingBack(ISipSession session) { - } - - public void onCallEstablished(ISipSession session, - String sessionDescription) { - } - - public void onCallEnded(ISipSession session) { - } - - public void onCallBusy(ISipSession session) { - } - - public void onCallTransferring(ISipSession session, - String sessionDescription) { - } - - public void onCallChangeFailed(ISipSession session, int errorCode, - String message) { - } - - public void onError(ISipSession session, int errorCode, String message) { - } - - public void onRegistering(ISipSession session) { - } - - public void onRegistrationDone(ISipSession session, int duration) { - } - - public void onRegistrationFailed(ISipSession session, int errorCode, - String message) { - } - - public void onRegistrationTimeout(ISipSession session) { - } -} diff --git a/voip/java/android/net/sip/package.html b/voip/java/android/net/sip/package.html deleted file mode 100644 index 3c4cc23..0000000 --- a/voip/java/android/net/sip/package.html +++ /dev/null @@ -1,45 +0,0 @@ -<HTML> -<BODY> -<p>Provides access to Session Initiation Protocol (SIP) functionality, such as -making and answering VOIP calls using SIP.</p> - -<p>For more information, see the -<a href="{@docRoot}guide/topics/connectivity/sip.html">Session Initiation Protocol</a> -developer guide.</p> -{@more} - -<p>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()}.</p> - -<p>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.</p> - -<p>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.</p> - -<p>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.</p> - -<p>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.</p> - -<p class="note"><strong>Note:</strong> -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. -</p> - -</BODY> -</HTML>
\ No newline at end of file diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java deleted file mode 100644 index c708be8..0000000 --- a/voip/java/com/android/server/sip/SipHelper.java +++ /dev/null @@ -1,537 +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 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.telephony.Rlog; - -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; -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.TransactionTerminatedEvent; -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 DBG = false; - private static final boolean DBG_PING = false; - - 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<ViaHeader> createViaHeaders() - throws ParseException, SipException { - List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(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 { - 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()); - - 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 { - 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(port); - uri.setTransportParam(transport); - } catch (InvalidArgumentException e) { - throw new RuntimeException(e); - } - return uri; - } - - public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, - String tag) throws SipException { - try { - 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("sendOptions()", 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); - - String replaceStr = Pattern.quote(userProfile.getUserName() + "@"); - SipURI requestURI = mAddressFactory.createSipURI( - userProfile.getUriString().replaceFirst(replaceStr, "")); - - List<ViaHeader> 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); - if (DBG) log("send request with challenge response: " - + ct.getRequest()); - ct.sendRequest(); - 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<ViaHeader> 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, 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 (DBG) log("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")); - - // 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 (DBG) log("send RE-INVITE: " + request); - dialog.sendRequest(clientTransaction); - return clientTransaction; - } catch (ParseException e) { - throw new SipException("sendReinvite()", e); - } - } - - public 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 (DBG) log("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, String externalIp, - int externalPort) throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse(Response.OK, - request); - response.addHeader(createContactHeader(localProfile, externalIp, - externalPort)); - response.setContent(sessionDescription, - mHeaderFactory.createContentTypeHeader( - "application", "sdp")); - - if (inviteTransaction == null) { - inviteTransaction = getServerTransaction(event); - } - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DBG) log("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 == null) { - inviteTransaction = getServerTransaction(event); - } - - if (inviteTransaction.getState() != TransactionState.COMPLETED) { - if (DBG) log("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 (DBG) log("send ACK: " + ack); - dialog.sendAck(ack); - } - - public void sendBye(Dialog dialog) throws SipException { - Request byeRequest = dialog.createRequest(Request.BYE); - if (DBG) log("send BYE: " + byeRequest); - dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); - } - - public void sendCancel(ClientTransaction inviteTransaction) - throws SipException { - Request cancelRequest = inviteTransaction.createCancel(); - if (DBG) log("send CANCEL: " + cancelRequest); - mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); - } - - public void sendResponse(RequestEvent event, int responseCode) - throws SipException { - try { - Request request = event.getRequest(); - Response response = mMessageFactory.createResponse( - responseCode, request); - if (DBG && (!Request.OPTIONS.equals(request.getMethod()) - || DBG_PING)) { - log("send response: " + response); - } - getServerTransaction(event).sendResponse(response); - } catch (ParseException e) { - throw new SipException("sendResponse()", e); - } - } - - 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 (DBG) log("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 { - Response response = mMessageFactory.createResponse( - Response.REQUEST_TERMINATED, inviteRequest); - if (DBG) log("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(); - } - - private void log(String s) { - Rlog.d(TAG, s); - } -} diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java deleted file mode 100644 index 80fe68c..0000000 --- a/voip/java/com/android/server/sip/SipService.java +++ /dev/null @@ -1,1262 +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 com.android.server.sip; - -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.PowerManager; -import android.os.Process; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -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.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; -import javax.sip.SipException; - -/** - * @hide - */ -public final class SipService extends ISipService.Stub { - static final String TAG = "SipService"; - 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; - 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; - private int mNetworkType = -1; - private SipWakeupTimer mTimer; - private WifiManager.WifiLock mWifiLock; - private boolean mSipOnWifiOnly; - - private SipKeepAliveProcessCallback mSipKeepAliveProcessCallback; - - private MyExecutor mExecutor = new MyExecutor(); - - // SipProfile URI --> group - private Map<String, SipSessionGroupExt> mSipGroups = - new HashMap<String, SipSessionGroupExt>(); - - // session ID --> session - private Map<String, ISipSession> mPendingSessions = - new HashMap<String, ISipSession>(); - - private ConnectivityReceiver mConnectivityReceiver; - 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 - * device. - */ - 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)); - if (DBG) slog("start:"); - } - } - - private SipService(Context context) { - if (DBG) log("SipService: 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); - } - - @Override - public synchronized SipProfile[] getListOfProfiles() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.USE_SIP, null); - boolean isCallerRadio = isCallerRadio(); - ArrayList<SipProfile> profiles = new ArrayList<SipProfile>(); - for (SipSessionGroupExt group : mSipGroups.values()) { - if (isCallerRadio || isCallerCreator(group)) { - profiles.add(group.getLocalProfile()); - } - } - return profiles.toArray(new SipProfile[profiles.size()]); - } - - @Override - public synchronized void open(SipProfile localProfile) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.USE_SIP, null); - localProfile.setCallingUid(Binder.getCallingUid()); - try { - createGroup(localProfile); - } catch (SipException e) { - loge("openToMakeCalls()", e); - // TODO: how to send the exception back - } - } - - @Override - 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) { - if (DBG) log("open3: incomingCallPendingIntent cannot be null; " - + "the profile is not opened"); - return; - } - if (DBG) log("open3: " + localProfile.getUriString() + ": " - + incomingCallPendingIntent + ": " + listener); - try { - SipSessionGroupExt group = createGroup(localProfile, - incomingCallPendingIntent, listener); - if (localProfile.getAutoRegistration()) { - group.openToReceiveCalls(); - updateWakeLocks(); - } - } catch (SipException e) { - loge("open3:", e); - // TODO: how to send the exception back - } - } - - 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); - } - - @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)) { - if (DBG) log("only creator or radio can close this profile"); - return; - } - - group = mSipGroups.remove(localProfileUri); - notifyProfileRemoved(group.getLocalProfile()); - group.close(); - - updateWakeLocks(); - } - - @Override - 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)) { - return true; - } else { - 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); - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group == null) return false; - if (isCallerCreatorOrRadio(group)) { - return group.isRegistered(); - } else { - 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( - android.Manifest.permission.USE_SIP, null); - SipSessionGroupExt group = mSipGroups.get(localProfileUri); - if (group == null) return; - if (isCallerCreator(group)) { - group.setListener(listener); - } else { - 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) { - if (DBG) log("createSession: mNetworkType==-1 ret=null"); - return null; - } - try { - SipSessionGroupExt group = createGroup(localProfile); - return group.createSession(listener); - } catch (SipException e) { - if (DBG) loge("createSession;", e); - return null; - } - } - - @Override - public synchronized ISipSession getPendingSession(String callId) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.USE_SIP, null); - 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) { - if (DBG) loge("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); - } else if (!isCallerCreator(group)) { - throw new SipException("only creator can access the profile"); - } - return group; - } - - private SipSessionGroupExt createGroup(SipProfile localProfile, - 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.setIncomingCallPendingIntent(incomingCallPendingIntent); - group.setListener(listener); - } else { - group = new SipSessionGroupExt(localProfile, - incomingCallPendingIntent, listener); - mSipGroups.put(key, group); - notifyProfileAdded(localProfile); - } - return group; - } - - private void notifyProfileAdded(SipProfile 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); - if (mSipGroups.size() == 1) { - registerReceivers(); - } - } - - private void notifyProfileRemoved(SipProfile 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); - if (mSipGroups.size() == 0) { - unregisterReceivers(); - } - } - - private void stopPortMappingMeasurement() { - if (mSipKeepAliveProcessCallback != null) { - mSipKeepAliveProcessCallback.stop(); - mSipKeepAliveProcessCallback = null; - } - } - - private void startPortMappingLifetimeMeasurement( - SipProfile localProfile) { - startPortMappingLifetimeMeasurement(localProfile, - DEFAULT_MAX_KEEPALIVE_INTERVAL); - } - - private void startPortMappingLifetimeMeasurement( - SipProfile localProfile, int maxInterval) { - if ((mSipKeepAliveProcessCallback == null) - && (mKeepAliveInterval == -1) - && isBehindNAT(mLocalIp)) { - if (DBG) log("startPortMappingLifetimeMeasurement: profile=" - + localProfile.getUriString()); - - int minInterval = mLastGoodKeepAliveInterval; - if (minInterval >= maxInterval) { - // If mLastGoodKeepAliveInterval also does not work, reset it - // to the default min - minInterval = mLastGoodKeepAliveInterval - = DEFAULT_KEEPALIVE_INTERVAL; - log(" reset min interval to " + minInterval); - } - mSipKeepAliveProcessCallback = new SipKeepAliveProcessCallback( - localProfile, minInterval, maxInterval); - mSipKeepAliveProcessCallback.start(); - } - } - - private void restartPortMappingLifetimeMeasurement( - SipProfile localProfile, int maxInterval) { - stopPortMappingMeasurement(); - mKeepAliveInterval = -1; - startPortMappingLifetimeMeasurement(localProfile, maxInterval); - } - - private synchronized void addPendingSession(ISipSession session) { - try { - cleanUpPendingSessions(); - mPendingSessions.put(session.getCallId(), session); - if (DBG) log("#pending sess=" + mPendingSessions.size()); - } catch (RemoteException e) { - // should not happen with a local call - loge("addPendingSession()", e); - } - } - - private void cleanUpPendingSessions() throws RemoteException { - Map.Entry<String, ISipSession>[] entries = - mPendingSessions.entrySet().toArray( - new Map.Entry[mPendingSessions.size()]); - for (Map.Entry<String, ISipSession> 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(); - for (SipSessionGroupExt group : mSipGroups.values()) { - if ((group != ringingGroup) && group.containsSession(callId)) { - if (DBG) log("call self: " - + ringingSession.getLocalProfile().getUriString() - + " -> " + group.getLocalProfile().getUriString()); - return true; - } - } - return false; - } - - private synchronized void onKeepAliveIntervalChanged() { - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onKeepAliveIntervalChanged(); - } - } - - private int getKeepAliveInterval() { - return (mKeepAliveInterval < 0) - ? mLastGoodKeepAliveInterval - : mKeepAliveInterval; - } - - 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 & d[0]) == 172) && - ((0x000000F0 & d[1]) == 16)) || - (((0x000000FF & d[0]) == 192) && - ((0x000000FF & d[1]) == 168))) { - return true; - } - } catch (UnknownHostException 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 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; - mAutoRegistration.setListener(listener); - } - - public SipProfile getLocalProfile() { - return mSipGroup.getLocalProfile(); - } - - public boolean containsSession(String callId) { - 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); - } - - private SipProfile duplicate(SipProfile p) { - try { - return new SipProfile.Builder(p).setPassword("*").build(); - } catch (Exception e) { - loge("duplicate()", e); - throw new RuntimeException("duplicate profile", e); - } - } - - public void setListener(ISipSessionListener listener) { - mAutoRegistration.setListener(listener); - } - - public void setIncomingCallPendingIntent(PendingIntent pIntent) { - mIncomingCallPendingIntent = pIntent; - } - - public void openToReceiveCalls() throws SipException { - mOpenedToReceiveCalls = true; - if (mNetworkType != -1) { - mSipGroup.openToReceiveCalls(this); - mAutoRegistration.start(mSipGroup); - } - 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 { - mSipGroup.close(); - mAutoRegistration.stop(); - } - } - - public void close() { - mOpenedToReceiveCalls = false; - mSipGroup.close(); - mAutoRegistration.stop(); - 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) { - 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; - } - - // send out incoming call broadcast - addPendingSession(session); - Intent intent = SipManager.createIncomingCallBroadcast( - session.getCallId(), sessionDescription); - 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) { - loge("onRinging: pendingIntent is canceled, drop incoming call", e); - session.endCall(); - } - } - } - - @Override - public void onError(ISipSession session, int errorCode, - String message) { - if (SSGE_DBG) log("onError: errorCode=" + errorCode + " desc=" - + SipErrorCode.toString(errorCode) + ": " + message); - } - - public boolean isOpenedToReceiveCalls() { - return mOpenedToReceiveCalls; - } - - public boolean isRegistered() { - return mAutoRegistration.isRegistered(); - } - - 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 SipKeepAliveProcessCallback implements Runnable, - SipSessionGroup.KeepAliveProcessCallback { - 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 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; - - public SipKeepAliveProcessCallback(SipProfile localProfile, - int minInterval, int maxInterval) { - mMaxInterval = maxInterval; - mMinInterval = minInterval; - mLocalProfile = localProfile; - } - - public void start() { - synchronized (SipService.this) { - 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()) { - if (SKAI_DBG) log("start: measurement aborted; interval=[" + - mMinInterval + "," + mMaxInterval + "]"); - return; - } - - try { - if (SKAI_DBG) log("start: 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 (Throwable t) { - onError(SipErrorCode.CLIENT_ERROR, t.toString()); - } - } - } - - public void stop() { - synchronized (SipService.this) { - if (mSession != null) { - mSession.stopKeepAliveProcess(); - mSession = null; - } - if (mGroup != null) { - mGroup.close(); - mGroup = null; - } - mTimer.cancel(this); - if (SKAI_DBG) log("stop"); - } - } - - private void restart() { - synchronized (SipService.this) { - // Return immediately if the measurement process is stopped - if (mSession == null) return; - - if (SKAI_DBG) log("restart: interval=" + mInterval); - try { - mSession.stopKeepAliveProcess(); - mPassCount = 0; - mSession.startKeepAliveProcess(mInterval, this); - } catch (SipException e) { - loge("restart", e); - } - } - } - - private boolean checkTermination() { - return ((mMaxInterval - mMinInterval) < MIN_INTERVAL); - } - - // SipSessionGroup.KeepAliveProcessCallback - @Override - public void onResponse(boolean portChanged) { - synchronized (SipService.this) { - if (!portChanged) { - 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 (SKAI_DBG) { - log("onResponse: portChanged=" + portChanged + " mKeepAliveInterval=" - + mKeepAliveInterval); - } - onKeepAliveIntervalChanged(); - } else { - // Since the rport is changed, shorten the interval. - mMaxInterval = mInterval; - } - 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 (SKAI_DBG) { - log("onResponse: checkTermination mKeepAliveInterval=" - + mKeepAliveInterval); - } - } else { - // calculate the new interval and continue. - mInterval = (mMaxInterval + mMinInterval) / 2; - if (SKAI_DBG) { - log("onResponse: mKeepAliveInterval=" + mKeepAliveInterval - + ", new mInterval=" + mInterval); - } - restart(); - } - } - } - - // SipSessionGroup.KeepAliveProcessCallback - @Override - public void onError(int errorCode, String description) { - if (SKAI_DBG) loge("onError: errorCode=" + errorCode + " desc=" + description); - restartLater(); - } - - // timeout handler - @Override - public void run() { - mTimer.cancel(this); - restart(); - } - - private void restartLater() { - synchronized (SipService.this) { - int interval = NAT_MEASUREMENT_RETRY_INTERVAL; - mTimer.cancel(this); - 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 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 SipSessionGroup.SipSessionImpl mSession; - private SipSessionGroup.SipSessionImpl mKeepAliveSession; - private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private int mBackoff = 1; - private boolean mRegistered; - private long mExpiryTime; - private int mErrorCode; - private String mErrorMessage; - private boolean mRunning = false; - - private int mKeepAliveSuccessCount = 0; - - public void start(SipSessionGroup group) { - if (!mRunning) { - mRunning = true; - 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 - mMyWakeLock.acquire(mSession); - mSession.unregister(); - SAR_TAG = "SipAutoReg:" + mSession.getLocalProfile().getUriString(); - if (SAR_DBG) log("start: group=" + group); - } - } - - private void startKeepAliveProcess(int interval) { - if (SAR_DBG) log("startKeepAliveProcess: interval=" + interval); - if (mKeepAliveSession == null) { - mKeepAliveSession = mSession.duplicate(); - } else { - mKeepAliveSession.stopKeepAliveProcess(); - } - try { - mKeepAliveSession.startKeepAliveProcess(interval, this); - } catch (SipException e) { - loge("startKeepAliveProcess: 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) { - int interval = getKeepAliveInterval(); - if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) { - 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 (SAR_DBG) { - log("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; - - // 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); - mSession.register(EXPIRY_TIME); - } - } - - // SipSessionGroup.KeepAliveProcessCallback - @Override - public void onError(int errorCode, String description) { - if (SAR_DBG) { - loge("onError: errorCode=" + errorCode + " desc=" + description); - } - onResponse(true); // re-register immediately - } - - public void stop() { - if (!mRunning) return; - mRunning = false; - mMyWakeLock.release(mSession); - if (mSession != null) { - mSession.setListener(null); - if (mNetworkType != -1 && mRegistered) mSession.unregister(); - } - - mTimer.cancel(this); - stopKeepAliveProcess(); - - mRegistered = false; - setListener(mProxy.getListener()); - } - - public void onKeepAliveIntervalChanged() { - if (mKeepAliveSession != null) { - int newInterval = getKeepAliveInterval(); - if (SAR_DBG) { - log("onKeepAliveIntervalChanged: interval=" + newInterval); - } - mKeepAliveSuccessCount = 0; - startKeepAliveProcess(newInterval); - } - } - - public void setListener(ISipSessionListener listener) { - synchronized (SipService.this) { - mProxy.setListener(listener); - - 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); - } - } else if (mNetworkType == -1) { - 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) { - loge("setListener: ", t); - } - } - } - - public boolean isRegistered() { - return mRegistered; - } - - // timeout handler: re-register - @Override - public void run() { - synchronized (SipService.this) { - if (!mRunning) return; - - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - if (SAR_DBG) log("run: registering"); - if (mNetworkType != -1) { - mMyWakeLock.acquire(mSession); - mSession.register(EXPIRY_TIME); - } - } - } - - private void restart(int duration) { - if (SAR_DBG) log("restart: duration=" + 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 (SAR_DBG) log("onRegistering: " + session); - synchronized (SipService.this) { - if (notCurrentSession(session)) return; - - mRegistered = false; - mProxy.onRegistering(session); - } - } - - private boolean notCurrentSession(ISipSession session) { - if (session != mSession) { - ((SipSessionGroup.SipSessionImpl) session).setListener(null); - mMyWakeLock.release(session); - return true; - } - return !mRunning; - } - - @Override - public void onRegistrationDone(ISipSession session, int duration) { - if (SAR_DBG) log("onRegistrationDone: " + session); - synchronized (SipService.this) { - if (notCurrentSession(session)) return; - - mProxy.onRegistrationDone(session, duration); - - if (duration > 0) { - 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); - - SipProfile localProfile = mSession.getLocalProfile(); - if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) - || localProfile.getSendKeepAlive())) { - startKeepAliveProcess(getKeepAliveInterval()); - } - } - mMyWakeLock.release(session); - } else { - mRegistered = false; - mExpiryTime = -1L; - if (SAR_DBG) log("Refresh registration immediately"); - run(); - } - } - } - - @Override - public void onRegistrationFailed(ISipSession session, int errorCode, - String message) { - if (SAR_DBG) log("onRegistrationFailed: " + session + ": " - + SipErrorCode.toString(errorCode) + ": " + message); - synchronized (SipService.this) { - if (notCurrentSession(session)) return; - - switch (errorCode) { - case SipErrorCode.INVALID_CREDENTIALS: - case SipErrorCode.SERVER_UNREACHABLE: - if (SAR_DBG) log(" pause auto-registration"); - stop(); - break; - default: - restartLater(); - } - - mErrorCode = errorCode; - mErrorMessage = message; - mProxy.onRegistrationFailed(session, errorCode, message); - mMyWakeLock.release(session); - } - } - - @Override - public void onRegistrationTimeout(ISipSession session) { - if (SAR_DBG) log("onRegistrationTimeout: " + session); - synchronized (SipService.this) { - if (notCurrentSession(session)) return; - - mErrorCode = SipErrorCode.TIME_OUT; - mProxy.onRegistrationTimeout(session); - restartLater(); - mMyWakeLock.release(session); - } - } - - 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 { - @Override - public void onReceive(Context context, Intent intent) { - Bundle bundle = intent.getExtras(); - if (bundle != null) { - final NetworkInfo info = (NetworkInfo) - bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO); - - // Run the handler in MyExecutor to be protected by wake lock - mExecutor.execute(new Runnable() { - @Override - public void run() { - onConnectivityChanged(info); - } - }); - } - } - } - - private void registerReceivers() { - mContext.registerReceiver(mConnectivityReceiver, - new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - if (DBG) log("registerReceivers:"); - } - - private void unregisterReceivers() { - mContext.unregisterReceiver(mConnectivityReceiver); - if (DBG) log("unregisterReceivers:"); - - // Reset variables maintained by ConnectivityReceiver. - mWifiLock.release(); - 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) { - // 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.getType() != mNetworkType) { - ConnectivityManager cm = (ConnectivityManager) - mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - info = cm.getActiveNetworkInfo(); - } - - // Some devices limit SIP on Wi-Fi. In this case, if we are not on - // Wi-Fi, treat it as a DISCONNECTED event. - 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 (mNetworkType == networkType) { - // TODO: Maybe we need to send seq/generation number - return; - } - if (DBG) { - log("onConnectivityChanged: " + mNetworkType + - " -> " + networkType); - } - - try { - if (mNetworkType != -1) { - mLocalIp = null; - stopPortMappingMeasurement(); - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(false); - } - } - mNetworkType = networkType; - - if (mNetworkType != -1) { - mLocalIp = determineLocalIp(); - mKeepAliveInterval = -1; - mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL; - for (SipSessionGroupExt group : mSipGroups.values()) { - group.onConnectivityChanged(true); - } - } - updateWakeLocks(); - } catch (SipException e) { - loge("onConnectivityChanged()", e); - } - } - - 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 implements Executor { - MyExecutor() { - super(createLooper()); - } - - @Override - public 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) { - executeInternal((Runnable) msg.obj); - } else { - if (DBG) log("handleMessage: not Runnable ignore msg=" + msg); - } - } - - private void executeInternal(Runnable task) { - try { - task.run(); - } catch (Throwable 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/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java deleted file mode 100644 index e820f35..0000000 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ /dev/null @@ -1,1863 +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 com.android.server.sip; - -import gov.nist.javax.sip.clientauthutils.AccountManager; -import gov.nist.javax.sip.clientauthutils.UserCredentials; -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; -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.telephony.Rlog; - -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.EventObject; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import javax.sip.ClientTransaction; -import javax.sip.Dialog; -import javax.sip.DialogTerminatedEvent; -import javax.sip.IOExceptionEvent; -import javax.sip.ObjectInUseException; -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.TransactionTerminatedEvent; -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; -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. - */ -class SipSessionGroup implements SipListener { - private static final String TAG = "SipSession"; - 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 - // 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 int END_CALL_TIMER = 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 - - private static final EventObject DEREGISTER = new EventObject("Deregister"); - private static final EventObject END_CALL = new EventObject("End call"); - - private final SipProfile mLocalProfile; - private final String mPassword; - - private SipStack mSipStack; - private SipHelper mSipHelper; - - // session that processes INVITE requests - private SipSessionImpl mCallReceiverSession; - private String mLocalIp; - - private SipWakeupTimer mWakeupTimer; - private SipWakeLock mWakeLock; - - // call-id-to-SipSession map - private Map<String, SipSessionImpl> mSessionMap = - new HashMap<String, SipSessionImpl>(); - - // external address observed from any response - private String mExternalIp; - private int mExternalPort; - - /** - * @param profile the local profile with password crossed out - * @param password the password of the profile - * @throws SipException if cannot assign requested address - */ - public SipSessionGroup(SipProfile profile, String password, - SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException { - mLocalProfile = profile; - mPassword = password; - mWakeupTimer = timer; - mWakeLock = wakeLock; - reset(); - } - - // TODO: remove this method once SipWakeupTimer can better handle variety - // of timeout values - void setWakeupTimer(SipWakeupTimer timer) { - mWakeupTimer = timer; - } - - 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); - mSipStack = SipFactory.getInstance().createSipStack(properties); - try { - SipProvider provider = mSipStack.createSipProvider( - mSipStack.createListeningPoint(local, port, protocol)); - provider.addSipListener(this); - mSipHelper = new SipHelper(mSipStack, provider); - } catch (SipException e) { - throw e; - } catch (Exception e) { - throw new SipException("failed to initialize SIP stack", e); - } - - if (DBG) log("reset: start stack for " + mLocalProfile.getUriString()); - mSipStack.start(); - } - - synchronized void onConnectivityChanged() { - 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"); - } - } - - synchronized void resetExternalAddress() { - if (DBG) { - log("resetExternalAddress: " + mSipStack); - } - mExternalIp = null; - mExternalPort = 0; - } - - public SipProfile getLocalProfile() { - return mLocalProfile; - } - - public String getLocalProfileUri() { - return mLocalProfile.getUriString(); - } - - private String getStackName() { - return "stack" + System.currentTimeMillis(); - } - - public synchronized void close() { - if (DBG) log("close: " + mLocalProfile.getUriString()); - onConnectivityChanged(); - mSessionMap.clear(); - closeToNotReceiveCalls(); - if (mSipStack != null) { - mSipStack.stop(); - mSipStack = null; - mSipHelper = null; - } - resetExternalAddress(); - } - - 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)); - } - - 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); - if ((session != null) && isLoggable(session)) { - if (DBG) log("getSipSession: event=" + key); - if (DBG) log("getSipSession: active sessions:"); - for (String k : mSessionMap.keySet()) { - if (DBG) log("getSipSession: ..." + 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)) { - if (DBG) log("addSipSession: key='" + key + "'"); - for (String k : mSessionMap.keySet()) { - if (DBG) log("addSipSession: " + 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)) { - if (DBG) log("removeSession: " + session + " is not associated with key '" - + key + "'"); - mSessionMap.put(key, s); - for (Map.Entry<String, SipSessionImpl> entry - : mSessionMap.entrySet()) { - if (entry.getValue() == s) { - key = entry.getKey(); - mSessionMap.remove(key); - } - } - } - - if ((s != null) && isLoggable(s)) { - if (DBG) log("removeSession: " + session + " @key '" + key + "'"); - for (String k : mSessionMap.keySet()) { - if (DBG) log("removeSession: " + k + ": " + mSessionMap.get(k)); - } - } - } - - @Override - public void processRequest(final RequestEvent event) { - if (isRequestEvent(Request.INVITE, event)) { - 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. - mWakeLock.acquire(WAKE_LOCK_HOLDING_TIME); - } - 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); - } - - 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("process: event new state after: " - + SipSession.State.toString(session.mState)); - } - } catch (Throwable e) { - loge("process: error event=" + event, getRootCause(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 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; - if (DBG) { - log("extractExternalAddress: 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, - 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 { - private static final String SSCRI_TAG = "SipSessionCallReceiverImpl"; - private static final boolean SSCRI_DBG = true; - - public SipSessionCallReceiverImpl(ISipSessionListener listener) { - super(listener); - } - - 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 (SSCRI_DBG) { - log("processNewInviteRequest: " + 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), - SipSession.State.INCOMING_CALL); - newSession.mProxy.onCallTransferring(newSession, - newSession.mPeerSessionDescription); - } else { - mSipHelper.sendResponse(event, response); - } - } else { - // New Incoming call. - newSession = createNewSession(event, mProxy, - mSipHelper.sendRinging(event, generateTag()), - SipSession.State.INCOMING_CALL); - mProxy.onRinging(newSession, newSession.mPeerProfile, - newSession.mPeerSessionDescription); - } - if (newSession != null) addSipSession(newSession); - } - - @Override - public boolean process(EventObject evt) throws SipException { - if (isLoggable(this, evt)) log("process: " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + logEvt(evt)); - if (isRequestEvent(Request.INVITE, evt)) { - processNewInviteRequest((RequestEvent) evt); - return true; - } else if (isRequestEvent(Request.OPTIONS, evt)) { - mSipHelper.sendResponse((RequestEvent) evt, Response.OK); - return true; - } else { - return false; - } - } - - private void log(String s) { - Rlog.d(SSCRI_TAG, s); - } - } - - 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 { - 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; - RequestEvent mInviteReceived; - Dialog mDialog; - ServerTransaction mServerTransaction; - ClientTransaction mClientTransaction; - String mPeerSessionDescription; - boolean mInCall; - SessionTimer mSessionTimer; - int mAuthenticationRetryCount; - - private SipKeepAlive mSipKeepAlive; - - private SipSessionImpl mSipSessionImpl; - - // the following three members are used for handling refer request. - SipSessionImpl mReferSession; - ReferredByHeader mReferredBy; - String mReplaces; - - // lightweight timer - class SessionTimer { - private boolean mRunning = true; - - void start(final int timeout) { - new Thread(new Runnable() { - @Override - 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) { - loge("session timer interrupted!", e); - } - } - } - - 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; - mPeerSessionDescription = null; - mAuthenticationRetryCount = 0; - mReferSession = null; - mReferredBy = null; - mReplaces = 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; - - cancelSessionTimer(); - - 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()); - } - - private Transaction getTransaction() { - if (mClientTransaction != null) return mClientTransaction; - if (mServerTransaction != null) return mServerTransaction; - return null; - } - - @Override - public int getState() { - return mState; - } - - @Override - 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() { - @Override - public void run() { - try { - processCommand(command); - } catch (Throwable e) { - loge("command error: " + command + ": " - + mLocalProfile.getUriString(), - getRootCause(e)); - onError(e); - } - } - }, "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; - doCommandAsync(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } - } - - @Override - public void endCall() { - doCommandAsync(END_CALL); - } - - @Override - public void changeCall(String sessionDescription, int timeout) { - synchronized (SipSessionGroup.this) { - if (mPeerProfile == null) return; - doCommandAsync(new MakeCallCommand(mPeerProfile, - sessionDescription, timeout)); - } - } - - @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("process cmd: " + command); - 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)); - } - - @Override - 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(" ~~~~~ " + this + ": " - + SipSession.State.toString(mState) + ": processing " - + logEvt(evt)); - synchronized (SipSessionGroup.this) { - if (isClosed()) return false; - - if (mSipKeepAlive != null) { - // event consumed by keepalive process - if (mSipKeepAlive.process(evt)) return true; - } - - Dialog dialog = null; - if (evt instanceof RequestEvent) { - dialog = ((RequestEvent) evt).getDialog(); - } else if (evt instanceof ResponseEvent) { - dialog = ((ResponseEvent) evt).getDialog(); - extractExternalAddress((ResponseEvent) evt); - } - if (dialog != null) mDialog = dialog; - - boolean processed; - - switch (mState) { - case SipSession.State.REGISTERING: - case SipSession.State.DEREGISTERING: - processed = registeringToReady(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; - case SipSession.State.ENDING_CALL: - processed = endingCall(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 { - if (SSI_DBG) log("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)) { - if (SSI_DBG) log("not the current transaction; current=" - + toString(current) + ", target=" + toString(target)); - return false; - } else if (current != null) { - if (SSI_DBG) log("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: - if (SSI_DBG) log("Transaction terminated; do nothing"); - break; - default: - if (SSI_DBG) log("Transaction terminated early: " + this); - onError(SipErrorCode.TRANSACTION_TERMINTED, - "transaction terminated"); - } - } - - private void processTimeout(TimeoutEvent event) { - if (SSI_DBG) log("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; - - default: - if (SSI_DBG) log(" do nothing"); - break; - } - } - - private int getExpiryTime(Response response) { - 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(); - } - if (time <= 0) { - time = EXPIRY_TIME; - } - expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME); - if (expires != null && time < expires.getExpires()) { - time = expires.getExpires(); - } - if (SSI_DBG) { - log("Expiry time = " + time); - } - return time; - } - - 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); - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - handleAuthentication(event); - 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) { - onError(SipErrorCode.SERVER_ERROR, - "server does not provide challenge"); - return false; - } else if (mAuthenticationRetryCount < 2) { - mClientTransaction = mSipHelper.handleChallenge( - event, getAccountManager()); - mDialog = mClientTransaction.getDialog(); - mAuthenticationRetryCount++; - if (isLoggable(this, event)) { - if (SSI_DBG) log(" authentication retry count=" - + mAuthenticationRetryCount); - } - return true; - } else { - if (crossDomainAuthenticationRequired(response)) { - onError(SipErrorCode.CROSS_DOMAIN_AUTHENTICATION, - getRealmFromResponse(response)); - } else { - onError(SipErrorCode.INVALID_CREDENTIALS, - "incorrect username or password"); - } - return false; - } - } - - 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() { - @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(); - } - }; - } - }; - } - - 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 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(); - 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()); - mProxy.onCalling(this); - 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); - mProxy.onRegistering(this); - return true; - } else if (DEREGISTER == evt) { - mState = SipSession.State.DEREGISTERING; - mClientTransaction = mSipHelper.sendRegister(mLocalProfile, - generateTag(), 0); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - 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 - mState = SipSession.State.INCOMING_CALL_ANSWERING; - mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, - mLocalProfile, - ((MakeCallCommand) evt).getSessionDescription(), - mServerTransaction, - mExternalIp, mExternalPort); - 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) { - // expect ACK, CANCEL request - if (isRequestEvent(Request.ACK, evt)) { - 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 - // 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: - 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; - cancelSessionTimer(); - mProxy.onRingingBack(this); - } - 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); - return true; - case Response.UNAUTHORIZED: - case Response.PROXY_AUTHENTICATION_REQUIRED: - if (handleAuthentication(event)) { - addSipSession(this); - } - return true; - case Response.REQUEST_PENDING: - // TODO: 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); - 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. - mState = SipSession.State.OUTGOING_CALL_CANCELING; - 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; - } - - 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 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 - if (END_CALL == evt) { - // rfc3261#section-15.1.1 - mState = SipSession.State.ENDING_CALL; - mSipHelper.sendBye(mDialog); - mProxy.onCallEnded(this); - startSessionTimer(END_CALL_TIMER); - return true; - } else if (isRequestEvent(Request.INVITE, evt)) { - // got Re-INVITE - mState = SipSession.State.INCOMING_CALL; - RequestEvent event = mInviteReceived = (RequestEvent) evt; - 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 (isRequestEvent(Request.REFER, evt)) { - return processReferRequest((RequestEvent) evt); - } else if (evt instanceof MakeCallCommand) { - // to change call - mState = SipSession.State.OUTGOING_CALL; - mClientTransaction = mSipHelper.sendReinvite(mDialog, - ((MakeCallCommand) evt).getSessionDescription()); - startSessionTimer(((MakeCallCommand) evt).getTimeout()); - return true; - } else if (evt instanceof ResponseEvent) { - if (expectResponse(Request.NOTIFY, evt)) return true; - } - 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) { - mSessionTimer = new SessionTimer(); - mSessionTimer.start(timeout); - } - } - - private void cancelSessionTimer() { - if (mSessionTimer != null) { - mSessionTimer.cancel(); - mSessionTimer = null; - } - } - - private String createErrorMessage(Response response) { - return String.format("%s (%d)", response.getReasonPhrase(), - response.getStatusCode()); - } - - private void enableKeepAlive() { - if (mSipSessionImpl != null) { - mSipSessionImpl.stopKeepAliveProcess(); - } else { - mSipSessionImpl = duplicate(); - } - try { - mSipSessionImpl.startKeepAliveProcess( - INCALL_KEEPALIVE_INTERVAL, mPeerProfile, null); - } catch (SipException e) { - loge("keepalive cannot be enabled; ignored", e); - mSipSessionImpl.stopKeepAliveProcess(); - } - } - - private void establishCall(boolean enableKeepAlive) { - mState = SipSession.State.IN_CALL; - cancelSessionTimer(); - if (!mInCall && enableKeepAlive) enableKeepAlive(); - mInCall = true; - mProxy.onCallEstablished(this, mPeerSessionDescription); - } - - 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: - 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 int getErrorCode(Throwable exception) { - String message = exception.getMessage(); - if (exception instanceof UnknownHostException) { - return SipErrorCode.SERVER_UNREACHABLE; - } else if (exception instanceof IOException) { - return SipErrorCode.SOCKET_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(Response response) { - int statusCode = response.getStatusCode(); - 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 (mSipKeepAlive != null) { - throw new SipException("Cannot create more than one " - + "keepalive process in a SipSession"); - } - mPeerProfile = peerProfile; - mSipKeepAlive = new SipKeepAlive(); - mProxy.setListener(mSipKeepAlive); - mSipKeepAlive.start(interval, callback); - } - } - - public void stopKeepAliveProcess() { - synchronized (SipSessionGroup.this) { - if (mSipKeepAlive != null) { - mSipKeepAlive.stop(); - mSipKeepAlive = null; - } - } - } - - 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; - - 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 (SKA_DBG) { - log("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) { - if (mRunning && (mState == SipSession.State.PINGING)) { - if (evt instanceof ResponseEvent) { - if (parseOptionsResult(evt)) { - if (mPortChanged) { - resetExternalAddress(); - 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 (DBG_PING) { - String peerUri = (mPeerProfile == null) - ? "null" - : mPeerProfile.getUriString(); - log("keepalive: " + mLocalProfile.getUriString() - + " --> " + peerUri + ", interval=" + mInterval); - } - try { - sendKeepAlive(); - } catch (Throwable t) { - if (SKA_DBG) { - loge("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 (SKA_DBG) { - log("stop keepalive:" + mLocalProfile.getUriString() - + ",RPort=" + mRPort); - } - mRunning = false; - mWakeupTimer.cancel(this); - reset(); - } - } - - private void sendKeepAlive() throws SipException { - 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 (SKA_DBG) log(String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (SKA_DBG) log("rport is the same: " + rPort); - } - } else { - if (SKA_DBG) log("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(); - } - - private void log(String s) { - Rlog.d(SKA_TAG, s); - } - } - - private void log(String s) { - Rlog.d(SSI_TAG, s); - } - } - - /** - * @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; - } - - private static SipProfile createPeerProfile(HeaderAddress header) - throws SipException { - try { - Address address = header.getAddress(); - SipURI uri = (SipURI) address.getURI(); - String username = uri.getUser(); - if (username == null) username = ANONYMOUS; - 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) { - throw new SipException("createPeerProfile()", e); - } - } - - private static boolean isLoggable(SipSessionImpl s) { - if (s != null) { - switch (s.mState) { - case SipSession.State.PINGING: - return DBG_PING; - } - } - return DBG; - } - - 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; - - if (evt instanceof ResponseEvent) { - Response response = ((ResponseEvent) evt).getResponse(); - if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { - return DBG_PING; - } - return DBG; - } else if (evt instanceof RequestEvent) { - if (isRequestEvent(Request.OPTIONS, evt)) { - return DBG_PING; - } - return DBG; - } - return false; - } - - private static String logEvt(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 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, int timeout) { - super(peerProfile); - mSessionDescription = sessionDescription; - mTimeout = timeout; - } - - public SipProfile getPeerProfile() { - return (SipProfile) getSource(); - } - - public String getSessionDescription() { - return mSessionDescription; - } - - public int getTimeout() { - return mTimeout; - } - } - - /** 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) { - 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(); - } - - @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) { - 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) { - 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/voip/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java deleted file mode 100644 index 7a4ae8d..0000000 --- a/voip/java/com/android/server/sip/SipSessionListenerProxy.java +++ /dev/null @@ -1,265 +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 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.telephony.Rlog; - -/** Class to help safely run a callback in a different thread. */ -class SipSessionListenerProxy extends ISipSessionListener.Stub { - private static final String TAG = "SipSessionListnerProxy"; - - 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(); - } - - @Override - public void onCalling(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - public void run() { - try { - mListener.onCalling(session); - } catch (Throwable t) { - handle(t, "onCalling()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onRinging()"); - } - } - }); - } - - @Override - public void onRingingBack(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - public void run() { - try { - mListener.onRingingBack(session); - } catch (Throwable t) { - handle(t, "onRingingBack()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onCallEstablished()"); - } - } - }); - } - - @Override - public void onCallEnded(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - public void run() { - try { - mListener.onCallEnded(session); - } catch (Throwable t) { - handle(t, "onCallEnded()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onCallTransferring()"); - } - } - }); - } - - @Override - public void onCallBusy(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - public void run() { - try { - mListener.onCallBusy(session); - } catch (Throwable t) { - handle(t, "onCallBusy()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onCallChangeFailed()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onError()"); - } - } - }); - } - - @Override - public void onRegistering(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - public void run() { - try { - mListener.onRegistering(session); - } catch (Throwable t) { - handle(t, "onRegistering()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onRegistrationDone()"); - } - } - }); - } - - @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); - } catch (Throwable t) { - handle(t, "onRegistrationFailed()"); - } - } - }); - } - - @Override - public void onRegistrationTimeout(final ISipSession session) { - if (mListener == null) return; - proxy(new Runnable() { - @Override - 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) { - 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/voip/java/com/android/server/sip/SipWakeLock.java b/voip/java/com/android/server/sip/SipWakeLock.java deleted file mode 100644 index b3fbb56..0000000 --- a/voip/java/com/android/server/sip/SipWakeLock.java +++ /dev/null @@ -1,73 +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 com.android.server.sip; - -import android.os.PowerManager; -import android.telephony.Rlog; - -import java.util.HashSet; - -class SipWakeLock { - private static final String TAG = "SipWakeLock"; - private static final boolean DBG = false; - private PowerManager mPowerManager; - private PowerManager.WakeLock mWakeLock; - private PowerManager.WakeLock mTimerWakeLock; - private HashSet<Object> mHolders = new HashSet<Object>(); - - SipWakeLock(PowerManager powerManager) { - mPowerManager = powerManager; - } - - synchronized void reset() { - if (DBG) log("reset count=" + mHolders.size()); - mHolders.clear(); - release(null); - } - - 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 (DBG) log("acquire count=" + mHolders.size()); - } - - synchronized void release(Object holder) { - mHolders.remove(holder); - if ((mWakeLock != null) && mHolders.isEmpty() - && mWakeLock.isHeld()) { - mWakeLock.release(); - } - if (DBG) log("release count=" + mHolders.size()); - } - - private void log(String s) { - Rlog.d(TAG, s); - } -} diff --git a/voip/java/com/android/server/sip/SipWakeupTimer.java b/voip/java/com/android/server/sip/SipWakeupTimer.java deleted file mode 100644 index 3ba43312..0000000 --- a/voip/java/com/android/server/sip/SipWakeupTimer.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * 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.telephony.Rlog; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.TreeSet; -import java.util.concurrent.Executor; - -/** - * Timer that can schedule events to occur even when the device is in sleep. - */ -class SipWakeupTimer extends BroadcastReceiver { - 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 Context mContext; - private AlarmManager mAlarmManager; - - // runnable --> time to execute in SystemClock - private TreeSet<MyEvent> mEventQueue = - new TreeSet<MyEvent>(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 boolean stopped() { - if (mEventQueue == null) { - if (DBG) log("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<MyEvent> newQueue = new TreeSet<MyEvent>( - mEventQueue.comparator()); - newQueue.addAll(mEventQueue); - mEventQueue.clear(); - mEventQueue = newQueue; - if (DBG) { - log("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 (DBG) { - log("set: add event " + event + " scheduled on " - + 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 (DBG) log("cancel:" + callback); - - MyEvent firstEvent = mEventQueue.first(); - for (Iterator<MyEvent> iter = mEventQueue.iterator(); - iter.hasNext();) { - MyEvent event = iter.next(); - if (event.mCallback == callback) { - iter.remove(); - if (DBG) log(" 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 (DBG) { - log("cancel: X"); - 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) { - // 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("onReceive: unrecognized intent: " + intent); - } - } - - private void printQueue() { - int count = 0; - for (MyEvent event : mEventQueue) { - log(" " + event + ": scheduled at " - + showTime(event.mTriggerTime) + ": last at " - + showTime(event.mLastTriggerTime)); - if (++count >= 5) break; - } - if (mEventQueue.size() > count) { - log(" ....."); - } else if (count == 0) { - log(" <empty>"); - } - } - - private void execute(long 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 (DBG) log("execute " + event); - - event.mLastTriggerTime = triggerTime; - event.mTriggerTime += event.mPeriod; - - // run the callback in the handler thread to prevent deadlock - mExecutor.execute(event.mCallback); - } - if (DBG) { - log("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; - } - } - - // 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<MyEvent> { - @Override - 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; - } - - @Override - public boolean equals(Object that) { - return (this == that); - } - } - - private void log(String s) { - Rlog.d(TAG, s); - } -} diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp deleted file mode 100644 index e2d820e..0000000 --- a/voip/jni/rtp/AmrCodec.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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 <string.h> - -#include "AudioCodec.h" - -#include "gsmamr_dec.h" -#include "gsmamr_enc.h" - -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, int count, 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. - const 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, int count, 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: - 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, int count, 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, int count, void *payload, int length) -{ - unsigned char *bytes = (unsigned char *)payload; - 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[n], MIME_IETF) != 31) { - break; - } - n += 160; - length -= 31; - bytes += 31; - } - return n; -} - -} // namespace - -AudioCodec *newAmrCodec() -{ - return new AmrCodec; -} - -AudioCodec *newGsmEfrCodec() -{ - return new GsmEfrCodec; -} diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk deleted file mode 100644 index b265cdd..0000000 --- a/voip/jni/rtp/Android.mk +++ /dev/null @@ -1,59 +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. -# - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE := librtp_jni - -LOCAL_SRC_FILES := \ - AudioCodec.cpp \ - AudioGroup.cpp \ - EchoSuppressor.cpp \ - RtpStream.cpp \ - util.cpp \ - rtp_jni.cpp - -LOCAL_SRC_FILES += \ - AmrCodec.cpp \ - G711Codec.cpp \ - GsmCodec.cpp - -LOCAL_SHARED_LIBRARIES := \ - libnativehelper \ - libcutils \ - libutils \ - libmedia \ - libstagefright_amrnb_common - -LOCAL_STATIC_LIBRARIES := libgsm libstagefright_amrnbdec libstagefright_amrnbenc - -LOCAL_C_INCLUDES += \ - $(JNI_H_INCLUDE) \ - external/libgsm/inc \ - 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 - - - -include $(BUILD_SHARED_LIBRARY) diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp deleted file mode 100644 index c75fbc9..0000000 --- a/voip/jni/rtp/AudioCodec.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 <strings.h> - -#include "AudioCodec.h" - -extern AudioCodec *newAlawCodec(); -extern AudioCodec *newUlawCodec(); -extern AudioCodec *newGsmCodec(); -extern AudioCodec *newAmrCodec(); -extern AudioCodec *newGsmEfrCodec(); - -struct AudioCodecType { - const char *name; - AudioCodec *(*create)(); -} gAudioCodecTypes[] = { - {"PCMA", newAlawCodec}, - {"PCMU", newUlawCodec}, - {"GSM", newGsmCodec}, - {"AMR", newAmrCodec}, - {"GSM-EFR", newGsmEfrCodec}, - {NULL, NULL}, -}; - -AudioCodec *newAudioCodec(const char *codecName) -{ - AudioCodecType *type = gAudioCodecTypes; - while (type->name != NULL) { - if (strcasecmp(codecName, type->name) == 0) { - AudioCodec *codec = type->create(); - codec->name = type->name; - return codec; - } - ++type; - } - return NULL; -} diff --git a/voip/jni/rtp/AudioCodec.h b/voip/jni/rtp/AudioCodec.h deleted file mode 100644 index 741730b..0000000 --- a/voip/jni/rtp/AudioCodec.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 <stdint.h> - -#ifndef __AUDIO_CODEC_H__ -#define __AUDIO_CODEC_H__ - -class AudioCodec -{ -public: - const char *name; - // Needed by destruction through base class pointers. - virtual ~AudioCodec() {} - // 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. - virtual int decode(int16_t *samples, int count, void *payload, int length) = 0; -}; - -AudioCodec *newAudioCodec(const char *codecName); - -#endif diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp deleted file mode 100644 index 2f0829e..0000000 --- a/voip/jni/rtp/AudioGroup.cpp +++ /dev/null @@ -1,1073 +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. - */ - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <errno.h> -#include <fcntl.h> -#include <sys/epoll.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/time.h> -#include <time.h> -#include <arpa/inet.h> -#include <netinet/in.h> - -// #define LOG_NDEBUG 0 -#define LOG_TAG "AudioGroup" -#include <cutils/atomic.h> -#include <cutils/properties.h> -#include <utils/Log.h> -#include <utils/Errors.h> -#include <utils/RefBase.h> -#include <utils/threads.h> -#include <utils/SystemClock.h> -#include <media/AudioSystem.h> -#include <media/AudioRecord.h> -#include <media/AudioTrack.h> -#include <media/mediarecorder.h> -#include <media/AudioEffect.h> -#include <audio_effects/effect_aec.h> -#include <system/audio.h> - -#include "jni.h" -#include "JNIHelp.h" - -#include "AudioCodec.h" -#include "EchoSuppressor.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 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 -// 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. -// + 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 only 8000Hz is supported. - -#define BUFFER_SIZE 2048 -#define HISTORY_SIZE 100 -#define MEASURE_BASE 100 -#define MEASURE_PERIOD 5000 -#define DTMF_PERIOD 200 - -class AudioStream -{ -public: - AudioStream(); - ~AudioStream(); - bool set(int mode, int socket, sockaddr_storage *remote, - AudioCodec *codec, 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; - bool mFixRemote; - - int mTick; - int mSampleRate; - int mSampleCount; - int mInterval; - int mKeepAlive; - - int16_t *mBuffer; - int mBufferMask; - int mBufferHead; - int mBufferTail; - int mLatencyTimer; - 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; - ALOGD("stream[%d] is dead", mSocket); -} - -bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, - AudioCodec *codec, int sampleRate, int sampleCount, - int codecType, int dtmfType) -{ - if (mode < 0 || mode > LAST_MODE) { - return false; - } - mMode = mode; - - 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 = 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. - read(gRandom, &mSequence, sizeof(mSequence)); - read(gRandom, &mTimestamp, sizeof(mTimestamp)); - read(gRandom, &mSsrc, sizeof(mSsrc)); - - mDtmfEvent = -1; - mDtmfStart = 0; - - // Only take over these things when succeeded. - mSocket = socket; - 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; - } - } - } - - ALOGD("stream[%d] is configured as %s %dkHz %dms mode %d", mSocket, - (codec ? codec->name : "RAW"), mSampleRate, mInterval, mMode); - 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; - ALOGV("stream[%d] skips %d packets", mSocket, skipped); - } - - tick = mTick; - mTick += mInterval; - ++mSequence; - mTimestamp += mSampleCount; - - // If there is an ongoing DTMF event, send it now. - if (mMode != RECEIVE_ONLY && mDtmfEvent != -1) { - int duration = mTimestamp - mDtmfStart; - // Make sure duration is reasonable. - if (duration >= 0 && duration < mSampleRate * DTMF_PERIOD) { - duration += mSampleCount; - int32_t buffer[4] = { - htonl(mDtmfMagic | mSequence), - htonl(mDtmfStart), - mSsrc, - htonl(mDtmfEvent | duration), - }; - if (duration >= mSampleRate * DTMF_PERIOD) { - buffer[3] |= htonl(1 << 23); - mDtmfEvent = -1; - } - sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT, - (sockaddr *)&mRemote, sizeof(mRemote)); - return; - } - mDtmfEvent = -1; - } - - int32_t buffer[mSampleCount + 3]; - bool data = false; - if (mMode != RECEIVE_ONLY) { - // Mix all other streams. - memset(buffer, 0, sizeof(buffer)); - while (chain) { - if (chain != this) { - data |= chain->mix(buffer, tick - mInterval, tick, mSampleRate); - } - chain = chain->mNext; - } - } - - 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; - } - if (sample > 32767) { - sample = 32767; - } - samples[i] = sample; - } - } else { - if ((mTick ^ mKeepAlive) >> 10 == 0) { - return; - } - mKeepAlive = mTick; - memset(samples, 0, sizeof(samples)); - - if (mMode != RECEIVE_ONLY) { - ALOGV("stream[%d] no data", mSocket); - } - } - - 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; - int length = mCodec->encode(&buffer[3], samples); - if (length <= 0) { - ALOGV("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 + BUFFER_SIZE - mBufferHead) > BUFFER_SIZE * 2) { - mBufferHead = tick - HISTORY_SIZE; - mBufferTail = mBufferHead; - } - - if (tick - mBufferHead > HISTORY_SIZE) { - // Throw away outdated samples. - mBufferHead = tick - HISTORY_SIZE; - if (mBufferTail - mBufferHead < 0) { - mBufferTail = mBufferHead; - } - } - - // 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; - mLatencyTimer = tick; - } else if (tick - mLatencyTimer >= MEASURE_PERIOD) { - ALOGV("stream[%d] reduces latency of %dms", mSocket, mLatencyScore); - mBufferTail -= mLatencyScore; - mLatencyScore = -1; - } - - int count = (BUFFER_SIZE - (mBufferTail - mBufferHead)) * mSampleRate; - if (count < mSampleCount) { - // Buffer overflow. Drop the packet. - ALOGV("stream[%d] buffer overflow", mSocket); - recv(mSocket, &c, 1, MSG_DONTWAIT); - return; - } - - // Receive the packet and decode it. - int16_t samples[count]; - if (!mCodec) { - // Special case for device stream. - count = recv(mSocket, samples, sizeof(samples), - MSG_TRUNC | MSG_DONTWAIT) >> 1; - } else { - __attribute__((aligned(4))) uint8_t buffer[2048]; - sockaddr_storage remote; - socklen_t addrlen = sizeof(remote); - - 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? - if (length < 12 || length > (int)sizeof(buffer) || - (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { - ALOGV("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, count, &buffer[offset], length); - } - if (length > 0 && mFixRemote) { - mRemote = remote; - mFixRemote = false; - } - count = length; - } - if (count <= 0) { - ALOGV("stream[%d] decoder error", mSocket); - return; - } - - if (tick - mBufferTail > 0) { - // Buffer underrun. Reset the jitter buffer. - ALOGV("stream[%d] buffer underrun", mSocket); - if (mBufferTail - mBufferHead <= 0) { - mBufferHead = tick + mInterval; - mBufferTail = mBufferHead; - } else { - int tail = (tick + mInterval) * mSampleRate; - for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) { - mBuffer[i & mBufferMask] = 0; - } - mBufferTail = tick + mInterval; - } - } - - // Append to the jitter buffer. - int tail = mBufferTail * mSampleRate; - for (int i = 0; i < count; ++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(AudioStream *stream); - bool platformHasAec() { return mPlatformHasAec; } - -private: - enum { - ON_HOLD = 0, - MUTED = 1, - NORMAL = 2, - ECHO_SUPPRESSION = 3, - LAST_MODE = 3, - }; - - bool checkPlatformAec(); - - AudioStream *mChain; - int mEventQueue; - volatile int mDtmfEvent; - - int mMode; - int mSampleRate; - int mSampleCount; - int mDeviceSocket; - bool mPlatformHasAec; - - class NetworkThread : public Thread - { - public: - NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {} - - bool start() - { - if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - ALOGE("cannot start network thread"); - return false; - } - return true; - } - - private: - AudioGroup *mGroup; - bool threadLoop(); - }; - sp<NetworkThread> mNetworkThread; - - class DeviceThread : public Thread - { - public: - DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {} - - bool start() - { - if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { - ALOGE("cannot start device thread"); - return false; - } - return true; - } - - private: - AudioGroup *mGroup; - bool threadLoop(); - }; - sp<DeviceThread> mDeviceThread; -}; - -AudioGroup::AudioGroup() -{ - mMode = ON_HOLD; - mChain = NULL; - mEventQueue = -1; - mDtmfEvent = -1; - mDeviceSocket = -1; - mNetworkThread = new NetworkThread(this); - mDeviceThread = new DeviceThread(this); - mPlatformHasAec = checkPlatformAec(); -} - -AudioGroup::~AudioGroup() -{ - mNetworkThread->requestExitAndWait(); - mDeviceThread->requestExitAndWait(); - close(mEventQueue); - close(mDeviceSocket); - while (mChain) { - AudioStream *next = mChain->mNext; - delete mChain; - mChain = next; - } - ALOGD("group[%d] is dead", mDeviceSocket); -} - -bool AudioGroup::set(int sampleRate, int sampleCount) -{ - mEventQueue = epoll_create(2); - if (mEventQueue == -1) { - ALOGE("epoll_create: %s", strerror(errno)); - return false; - } - - mSampleRate = sampleRate; - mSampleCount = sampleCount; - - // Create device socket. - int pair[2]; - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { - ALOGE("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]); - ALOGE("cannot initialize device stream"); - return false; - } - - // 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))) { - ALOGE("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)) { - ALOGE("epoll_ctl: %s", strerror(errno)); - return false; - } - - // Anything else? - ALOGD("stream[%d] joins group[%d]", pair[1], pair[0]); - return true; -} - -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 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") || !strcmp(value, "tuna"))) { - mode = ECHO_SUPPRESSION; - } - if (mMode == mode) { - return true; - } - - mDeviceThread->requestExitAndWait(); - ALOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); - mMode = mode; - return (mode == ON_HOLD) || mDeviceThread->start(); -} - -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)) { - ALOGE("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; - } - - ALOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); - return true; -} - -bool AudioGroup::remove(AudioStream *stream) -{ - mNetworkThread->requestExitAndWait(); - - 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; - } - chain->mNext = stream->mNext; - ALOGD("stream[%d] leaves group[%d]", stream->mSocket, mDeviceSocket); - delete stream; - break; - } - } - - // Do not start network thread if there is only one stream. - if (!mChain->mNext || !mNetworkThread->start()) { - return false; - } - return true; -} - -bool AudioGroup::NetworkThread::threadLoop() -{ - AudioStream *chain = mGroup->mChain; - int tick = elapsedRealtime(); - int deadline = tick + 10; - int count = 0; - - for (AudioStream *stream = chain; stream; stream = stream->mNext) { - if (tick - stream->mTick >= 0) { - stream->encode(tick, chain); - } - if (deadline - stream->mTick > 0) { - deadline = stream->mTick; - } - ++count; - } - - int event = mGroup->mDtmfEvent; - if (event != -1) { - for (AudioStream *stream = chain; stream; stream = stream->mNext) { - stream->sendDtmf(event); - } - mGroup->mDtmfEvent = -1; - } - - deadline -= tick; - if (deadline < 1) { - deadline = 1; - } - - epoll_event events[count]; - count = epoll_wait(mGroup->mEventQueue, events, count, deadline); - if (count == -1) { - ALOGE("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::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; - int sampleRate = mGroup->mSampleRate; - int sampleCount = mGroup->mSampleCount; - int deviceSocket = mGroup->mDeviceSocket; - - // Find out the frame count for AudioTrack and AudioRecord. - 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, - AUDIO_FORMAT_PCM_16_BIT, AUDIO_CHANNEL_IN_MONO) != NO_ERROR || input <= 0) { - ALOGE("cannot compute frame count"); - return false; - } - ALOGD("reported frame count: output %d, input %d", output, input); - - if (output < sampleCount * 2) { - output = sampleCount * 2; - } - if (input < sampleCount * 2) { - input = sampleCount * 2; - } - ALOGD("adjusted frame count: output %d, input %d", output, input); - - // Initialize AudioTrack and AudioRecord. - 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) { - ALOGE("cannot initialize audio device"); - return false; - } - 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)); - setsockopt(deviceSocket, SOL_SOCKET, SO_SNDBUF, &output, sizeof(output)); - - // Drain device socket. - 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) { - record.start(); - int16_t one; - record.read(&one, sizeof(one)); - } - track.start(); - - while (!exitPending()) { - int16_t output[sampleCount]; - if (recv(deviceSocket, output, sizeof(output), 0) <= 0) { - memset(output, 0, sizeof(output)); - } - - 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) { - ALOGE("cannot write to AudioTrack"); - goto exit; - } - } - - if (toRead > 0) { - AudioRecord::Buffer buffer; - buffer.frameCount = toRead; - - status_t status = record.obtainBuffer(&buffer, 1); - if (status == NO_ERROR) { - 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) { - ALOGE("cannot read from AudioRecord"); - goto exit; - } - } - } - - if (chances <= 0) { - ALOGW("device loop timeout"); - while (recv(deviceSocket, &c, 1, MSG_DONTWAIT) == 1); - } - - if (mode != MUTED) { - if (echo != NULL) { - ALOGV("echo->run()"); - echo->run(output, input); - } - send(deviceSocket, input, sizeof(input), MSG_DONTWAIT); - } - } - -exit: - delete echo; - delete aec; - return true; -} - -//------------------------------------------------------------------------------ - -static jfieldID gNative; -static jfieldID gMode; - -int add(JNIEnv *env, jobject thiz, jint mode, - jint socket, jstring jRemoteAddress, jint remotePort, - jstring jCodecSpec, jint dtmfType) -{ - AudioCodec *codec = NULL; - AudioStream *stream = NULL; - AudioGroup *group = NULL; - - // Sanity check. - sockaddr_storage remote; - if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { - // Exception already thrown. - return 0; - } - if (!jCodecSpec) { - jniThrowNullPointerException(env, "codecSpec"); - return 0; - } - const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL); - if (!codecSpec) { - // Exception already thrown. - return 0; - } - socket = dup(socket); - if (socket == -1) { - jniThrowException(env, "java/lang/IllegalStateException", - "cannot get stream socket"); - return 0; - } - - // Create audio codec. - int codecType = -1; - char codecName[16]; - int sampleRate = -1; - sscanf(codecSpec, "%d %15[^/]%*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, codec, sampleRate, sampleCount, - codecType, dtmfType)) { - jniThrowException(env, "java/lang/IllegalStateException", - "cannot initialize audio stream"); - goto error; - } - socket = -1; - codec = NULL; - - // 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); - return (int)stream; - -error: - delete group; - delete stream; - delete codec; - close(socket); - env->SetIntField(thiz, gNative, 0); - return 0; -} - -void remove(JNIEnv *env, jobject thiz, jint stream) -{ - AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); - if (group) { - if (!stream || !group->remove((AudioStream *)stream)) { - delete group; - env->SetIntField(thiz, gNative, 0); - } - } -} - -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); - } -} - -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[] = { - {"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}, -}; - -} // namespace - -int registerAudioGroup(JNIEnv *env) -{ - gRandom = open("/dev/urandom", O_RDONLY); - if (gRandom == -1) { - ALOGE("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) { - ALOGE("JNI registration failed"); - return -1; - } - return 0; -} diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp deleted file mode 100644 index e223136..0000000 --- a/voip/jni/rtp/EchoSuppressor.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 <stdio.h> -#include <string.h> -#include <stdint.h> -#include <string.h> -#include <math.h> - -#define LOG_TAG "Echo" -#include <utils/Log.h> - -#include "EchoSuppressor.h" - -// 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) -{ - tailLength += sampleCount * 4; - - int shift = 0; - while ((sampleCount >> shift) > 1 && (tailLength >> shift) > 256) { - ++shift; - } - - mShift = shift + 4; - mScale = 1 << shift; - mSampleCount = sampleCount; - mWindowSize = sampleCount >> shift; - mTailLength = tailLength >> shift; - mRecordLength = tailLength * 2 / sampleCount; - mRecordOffset = 0; - - 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 [] mXSums; - delete [] mX2Sums; - delete [] mXRecords; - delete [] mYRecords; - delete [] mY2Records; - delete [] mXYSums; - delete [] mXYRecords; -} - -void EchoSuppressor::run(int16_t *playbacked, int16_t *recorded) -{ - // Update Xs. - for (int i = mTailLength - 1; i >= 0; --i) { - mXs[i + mWindowSize] = mXs[i]; - } - for (int i = mWindowSize - 1, j = 0; i >= 0; --i, j += mScale) { - uint32_t sum = 0; - for (int k = 0; k < mScale; ++k) { - int32_t x = playbacked[j + k] << 15; - mLastX += x; - sum += ((mLastX >= 0) ? mLastX : -mLastX) >> 15; - mLastX -= (mLastX >> 10) + x; - } - mXs[i] = sum >> mShift; - } - - // Update XSums, X2Sums, and XRecords. - for (int i = mTailLength - mWindowSize - 1; i >= 0; --i) { - mXSums[i + mWindowSize] = mXSums[i]; - mX2Sums[i + mWindowSize] = mX2Sums[i]; - } - 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. - 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) { - int32_t y = recorded[j + k] << 15; - mLastY += y; - sum += ((mLastY >= 0) ? mLastY : -mLastY) >> 15; - mLastY -= (mLastY >> 10) + y; - } - ys[i] = sum >> mShift; - } - - // 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]; - } - 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; - } - - // Compute correlations. - 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 cov = mXYSums[i] - mWeight * mXSums[i] * mYSum; - 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; - } - } - } - //ALOGI("corr^2 %.5f, var %8.0f %8.0f, latency %d", corr2, varX, varY, - // latency * mScale); - - // Do echo suppression. - 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; - } - } - - // Increase RecordOffset. - ++mRecordOffset; - if (mRecordOffset == mRecordLength) { - mRecordOffset = 0; - } -} diff --git a/voip/jni/rtp/EchoSuppressor.h b/voip/jni/rtp/EchoSuppressor.h deleted file mode 100644 index 2f3b593..0000000 --- a/voip/jni/rtp/EchoSuppressor.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 <stdint.h> - -class EchoSuppressor -{ -public: - // The sampleCount must be power of 2. - EchoSuppressor(int sampleCount, int tailLength); - ~EchoSuppressor(); - void run(int16_t *playbacked, int16_t *recorded); - -private: - int mShift; - int mScale; - int mSampleCount; - int mWindowSize; - int mTailLength; - int mRecordLength; - int mRecordOffset; - - uint16_t *mXs; - uint32_t *mXSums; - uint32_t *mX2Sums; - uint16_t *mXRecords; - - uint32_t mYSum; - uint32_t mY2Sum; - uint32_t *mYRecords; - uint32_t *mY2Records; - - uint32_t *mXYSums; - uint32_t *mXYRecords; - - int32_t mLastX; - int32_t mLastY; - - float mWeight; -}; - -#endif diff --git a/voip/jni/rtp/G711Codec.cpp b/voip/jni/rtp/G711Codec.cpp deleted file mode 100644 index ef54863..0000000 --- a/voip/jni/rtp/G711Codec.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 { - -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, - 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, int count, 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, 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; - 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, int count, 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, 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; - 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; -} diff --git a/voip/jni/rtp/GsmCodec.cpp b/voip/jni/rtp/GsmCodec.cpp deleted file mode 100644 index 61dfdc9..0000000 --- a/voip/jni/rtp/GsmCodec.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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, int count, 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, int count, void *payload, int length) -{ - 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 n; -} - -} // namespace - -AudioCodec *newGsmCodec() -{ - return new GsmCodec; -} diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp deleted file mode 100644 index bfe8e24..0000000 --- a/voip/jni/rtp/RtpStream.cpp +++ /dev/null @@ -1,113 +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. - */ - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> - -#define LOG_TAG "RtpStream" -#include <utils/Log.h> - -#include "jni.h" -#include "JNIHelp.h" - -extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); - -namespace { - -jfieldID gSocket; - -jint create(JNIEnv *env, jobject thiz, jstring jAddress) -{ - env->SetIntField(thiz, gSocket, -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, gSocket, 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, gSocket, socket); - return port; - } - } - } - - jniThrowException(env, "java/net/SocketException", strerror(errno)); - ::close(socket); - return -1; -} - -void close(JNIEnv *env, jobject thiz) -{ - int socket = env->GetIntField(thiz, gSocket); - ::close(socket); - env->SetIntField(thiz, gSocket, -1); -} - -JNINativeMethod gMethods[] = { - {"create", "(Ljava/lang/String;)I", (void *)create}, - {"close", "()V", (void *)close}, -}; - -} // namespace - -int registerRtpStream(JNIEnv *env) -{ - jclass clazz; - if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || - (gSocket = env->GetFieldID(clazz, "mSocket", "I")) == NULL || - env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { - ALOGE("JNI registration failed"); - return -1; - } - return 0; -} diff --git a/voip/jni/rtp/rtp_jni.cpp b/voip/jni/rtp/rtp_jni.cpp deleted file mode 100644 index 9f4bff9..0000000 --- a/voip/jni/rtp/rtp_jni.cpp +++ /dev/null @@ -1,32 +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. - */ - -#include <stdio.h> - -#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/voip/jni/rtp/util.cpp b/voip/jni/rtp/util.cpp deleted file mode 100644 index 1d702fc..0000000 --- a/voip/jni/rtp/util.cpp +++ /dev/null @@ -1,61 +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. - */ - -#include <stdio.h> -#include <string.h> -#include <arpa/inet.h> -#include <netinet/in.h> - -#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; -} |