M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.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
* @hide
*/
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);
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.rtp;
import java.util.HashMap;
import java.util.Map;
/**
* An AudioGroup acts as a router connected to the speaker, the microphone, and
* {@link AudioStream}s. Its pipeline has four steps. First, for each
* AudioStream not in {@link RtpStream#MODE_SEND_ONLY}, decodes its incoming
* packets and stores in its buffer. Then, if the microphone is enabled,
* processes the recorded audio and stores in its buffer. Third, if the speaker
* is enabled, mixes and playbacks buffers of all AudioStreams. Finally, for
* each AudioStream not in {@link RtpStream#MODE_RECEIVE_ONLY}, mixes all other
* buffers and sends back the encoded packets. An AudioGroup does nothing if
* there is no AudioStream in it.
*
* <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 not only
* doubles the load but also the bandwidth. The condition varies from one device
* to another, and developers must choose the right combination in order to get
* the best result.
*
* <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
* previous call to talk to each other. This can be done easily using two
* AudioGroups, but there are some limitations. Since the speaker and the
* microphone are shared globally, only one AudioGroup is allowed to run in
* modes other than {@link #MODE_ON_HOLD}. In addition, before adding an
* AudioStream into an AudioGroup, one should always put all other AudioGroups
* into {@link #MODE_ON_HOLD}. That will make sure the audio driver correctly
* initialized.
* @hide
*/
public class AudioGroup {
/**
* This mode is similar to {@link #MODE_NORMAL} except the speaker and
* the microphone are disabled.
*/
public static final int MODE_ON_HOLD = 0;
/**
* This mode is similar to {@link #MODE_NORMAL} except the microphone is
* muted.
*/
public static final int MODE_MUTED = 1;
/**
* This mode indicates that the speaker, the microphone, and all
* {@link AudioStream}s in the group are enabled. First, the packets
* received from the streams are decoded and mixed with the audio recorded
* from the microphone. Then, the results are played back to the speaker,
* encoded and sent back to each stream.
*/
public static final int MODE_NORMAL = 2;
/**
* This mode is similar to {@link #MODE_NORMAL} except the echo suppression
* is enabled. It should be only used when the speaker phone is on.
*/
public static final int MODE_ECHO_SUPPRESSION = 3;
private final Map<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 current mode.
*/
public int getMode() {
return mMode;
}
/**
* Changes the current mode. It must be one of {@link #MODE_ON_HOLD},
* {@link #MODE_MUTED}, {@link #MODE_NORMAL}, and
* {@link #MODE_ECHO_SUPPRESSION}.
*
* @param mode The mode to change to.
* @throws IllegalArgumentException if the mode is invalid.
*/
public synchronized native void setMode(int mode);
private native void add(int mode, int socket, String remoteAddress,
int remotePort, String codecSpec, int dtmfType);
synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) {
if (!mStreams.containsKey(stream)) {
try {
int socket = stream.dup();
String codecSpec = String.format("%d %s %s", codec.type,
codec.rtpmap, codec.fmtp);
add(stream.getMode(), socket,
stream.getRemoteAddress().getHostAddress(),
stream.getRemotePort(), codecSpec, dtmfType);
mStreams.put(stream, socket);
} catch (NullPointerException e) {
throw new IllegalStateException(e);
}
}
}
private native void remove(int socket);
synchronized void remove(AudioStream stream) {
Integer socket = mStreams.remove(stream);
if (socket != null) {
remove(socket);
}
}
/**
* Sends a DTMF digit to every {@link AudioStream} in this group. Currently
* only event {@code 0} to {@code 15} are supported.
*
* @throws IllegalArgumentException if the event is invalid.
*/
public native synchronized void sendDtmf(int event);
/**
* Removes every {@link AudioStream} in this group.
*/
public synchronized void clear() {
remove(-1);
}
@Override
protected void finalize() throws Throwable {
clear();
super.finalize();
}
}

View File

@@ -0,0 +1,162 @@
/*
* 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, users 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.
*
* @see AudioGroup
* @hide
*/
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) {
if (mGroup == group) {
return;
}
if (mGroup != null) {
mGroup.remove(this);
mGroup = null;
}
if (group != null) {
group.add(this, mCodec, mDtmfType);
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 (type == mCodec.type) {
throw new IllegalArgumentException("The type is used by codec");
}
}
mDtmfType = type;
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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).
* @hide
*/
public class RtpStream {
/**
* This mode indicates that the stream sends and receives packets at the
* same time. This is the initial mode for new streams.
*/
public static final int MODE_NORMAL = 0;
/**
* This mode indicates that the stream only sends packets.
*/
public static final int MODE_SEND_ONLY = 1;
/**
* This mode indicates that the stream only receives packets.
*/
public static final int MODE_RECEIVE_ONLY = 2;
private final InetAddress mLocalAddress;
private final int mLocalPort;
private InetAddress mRemoteAddress;
private int mRemotePort = -1;
private int mMode = MODE_NORMAL;
private int mNative;
static {
System.loadLibrary("rtp_jni");
}
/**
* Creates a RtpStream on the given local address. Note that the local
* port is assigned automatically to conform with RFC 3550.
*
* @param address The network address of the local host to bind to.
* @throws SocketException if the address cannot be bound or a problem
* occurs during binding.
*/
RtpStream(InetAddress address) throws SocketException {
mLocalPort = create(address.getHostAddress());
mLocalAddress = address;
}
private native int create(String address) throws SocketException;
/**
* Returns the network address of the local host.
*/
public InetAddress getLocalAddress() {
return mLocalAddress;
}
/**
* Returns the network port of the local host.
*/
public int getLocalPort() {
return mLocalPort;
}
/**
* Returns the network address of the remote host or {@code null} if the
* stream is not associated.
*/
public InetAddress getRemoteAddress() {
return mRemoteAddress;
}
/**
* Returns the network port of the remote host or {@code -1} if the stream
* is not associated.
*/
public int getRemotePort() {
return mRemotePort;
}
/**
* Returns {@code true} if the stream is busy. 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 != MODE_NORMAL && mode != MODE_SEND_ONLY && mode != MODE_RECEIVE_ONLY) {
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;
}
synchronized native int dup();
/**
* Releases allocated resources. The stream becomes inoperable after calling
* this method.
*
* @throws IllegalStateException if the stream is busy.
* @see #isBusy()
*/
public void release() {
if (isBusy()) {
throw new IllegalStateException("Busy");
}
close();
}
private synchronized native void close();
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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();
}

View File

@@ -0,0 +1,148 @@
/*
* 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);
}

View File

@@ -0,0 +1,125 @@
/*
* 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 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);
}

View File

@@ -0,0 +1,612 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
import java.util.ArrayList;
import java.util.Arrays;
/**
* An object used to manipulate messages of Session Description Protocol (SDP).
* It is mainly designed for the uses of Session Initiation Protocol (SIP).
* Therefore, it only handles connection addresses ("c="), bandwidth limits,
* ("b="), encryption keys ("k="), and attribute fields ("a="). Currently this
* implementation does not support multicast sessions.
*
* <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("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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
/*
* 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() {
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
/**
* 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);
}
}

View File

@@ -0,0 +1,608 @@
/*
* 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.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
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.<br/><br/>Your application must also request the {@link
* android.Manifest.permission#INTERNET} and {@link android.Manifest.permission#USE_SIP}
* permissions.</p>
*/
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()));
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()));
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 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?
Log.w(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");
}
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2010, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
parcelable SipProfile;

View File

@@ -0,0 +1,496 @@
/*
* 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>
*/
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 auth. name of the profile
* @return this builder object
* @hide // TODO: remove when we make it public
*/
public Builder setAuthUserName(String name) {
mProfile.mAuthUserName = name;
return this;
}
/**
* Sets the name of the profile. This name is given by user.
*
* @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
* should be used in authentication instead.
*
* @return the auth. username
* @hide // TODO: remove when we make it public
*/
public String getAuthUserName() {
return mAuthUserName;
}
/**
* Gets the password.
*
* @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;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
/**
* Listener 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);
}

View File

@@ -0,0 +1,533 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.sip;
import android.os.RemoteException;
import android.util.Log;
/**
* 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;
/** 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 an error occurs during session initialization and
* termination.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onError(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when an error occurs during session modification negotiation.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onCallChangeFailed(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when a registration request is sent.
*
* @param session the session object that carries out the transaction
*/
public void onRegistering(SipSession session) {
}
/**
* Called when registration is successfully done.
*
* @param session the session object that carries out the transaction
* @param duration duration in second before the registration expires
*/
public void onRegistrationDone(SipSession session, int duration) {
}
/**
* Called when the registration fails.
*
* @param session the session object that carries out the transaction
* @param errorCode error code defined in {@link SipErrorCode}
* @param errorMessage error message
*/
public void onRegistrationFailed(SipSession session, int errorCode,
String errorMessage) {
}
/**
* Called when the registration gets timed out.
*
* @param session the session object that carries out the transaction
*/
public void onRegistrationTimeout(SipSession session) {
}
}
private final ISipSession mSession;
private Listener mListener;
SipSession(ISipSession realSession) {
mSession = realSession;
if (realSession != null) {
try {
realSession.setListener(createListener());
} catch (RemoteException e) {
Log.e(TAG, "SipSession.setListener(): " + e);
}
}
}
SipSession(ISipSession realSession, Listener listener) {
this(realSession);
setListener(listener);
}
/**
* Gets the IP address of the local host on which this SIP session runs.
*
* @return the IP address of the local host
*/
public String getLocalIp() {
try {
return mSession.getLocalIp();
} catch (RemoteException e) {
Log.e(TAG, "getLocalIp(): " + e);
return "127.0.0.1";
}
}
/**
* Gets the SIP profile that this session is associated with.
*
* @return the SIP profile that this session is associated with
*/
public SipProfile getLocalProfile() {
try {
return mSession.getLocalProfile();
} catch (RemoteException e) {
Log.e(TAG, "getLocalProfile(): " + e);
return null;
}
}
/**
* Gets the SIP profile that this session is connected to. Only available
* when the session is associated with a SIP dialog.
*
* @return the SIP profile that this session is connected to
*/
public SipProfile getPeerProfile() {
try {
return mSession.getPeerProfile();
} catch (RemoteException e) {
Log.e(TAG, "getPeerProfile(): " + e);
return null;
}
}
/**
* Gets the session state. The value returned must be one of the states in
* {@link State}.
*
* @return the session state
*/
public int getState() {
try {
return mSession.getState();
} catch (RemoteException e) {
Log.e(TAG, "getState(): " + e);
return State.NOT_DEFINED;
}
}
/**
* Checks if the session is in a call.
*
* @return true if the session is in a call
*/
public boolean isInCall() {
try {
return mSession.isInCall();
} catch (RemoteException e) {
Log.e(TAG, "isInCall(): " + e);
return false;
}
}
/**
* Gets the call ID of the session.
*
* @return the call ID
*/
public String getCallId() {
try {
return mSession.getCallId();
} catch (RemoteException e) {
Log.e(TAG, "getCallId(): " + e);
return null;
}
}
/**
* Sets the listener to listen to the session events. A {@code SipSession}
* can only hold one listener at a time. Subsequent calls to this method
* override the previous listener.
*
* @param listener to listen to the session events of this object
*/
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Performs registration to the server specified by the associated local
* profile. The session listener is called back upon success or failure of
* registration. The method is only valid to call when the session state is
* in {@link 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) {
Log.e(TAG, "register(): " + e);
}
}
/**
* Performs unregistration to the server specified by the associated local
* profile. Unregistration is technically the same as registration with zero
* expiration duration. The session listener is called back upon success or
* failure of unregistration. The method is only valid to call when the
* session state is in {@link State#READY_TO_CALL}.
*
* @see Listener
*/
public void unregister() {
try {
mSession.unregister();
} catch (RemoteException e) {
Log.e(TAG, "unregister(): " + e);
}
}
/**
* Initiates a call to the specified profile. The session listener is called
* back upon defined session events. The method is only valid to call when
* the session state is in {@link 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) {
Log.e(TAG, "makeCall(): " + e);
}
}
/**
* Answers an incoming call with the specified session description. The
* method is only valid to call when the session state is in
* {@link 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) {
Log.e(TAG, "answerCall(): " + e);
}
}
/**
* Ends an established call, terminates an outgoing call or rejects an
* incoming call. The method is only valid to call when the session state is
* in {@link 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) {
Log.e(TAG, "endCall(): " + e);
}
}
/**
* Changes the session description during a call. The method is only valid
* to call when the session state is in {@link 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) {
Log.e(TAG, "changeCall(): " + e);
}
}
ISipSession getRealSession() {
return mSession;
}
private ISipSessionListener createListener() {
return new ISipSessionListener.Stub() {
public void onCalling(ISipSession session) {
if (mListener != null) {
mListener.onCalling(SipSession.this);
}
}
public void onRinging(ISipSession session, SipProfile caller,
String sessionDescription) {
if (mListener != null) {
mListener.onRinging(SipSession.this, caller,
sessionDescription);
}
}
public void onRingingBack(ISipSession session) {
if (mListener != null) {
mListener.onRingingBack(SipSession.this);
}
}
public void onCallEstablished(ISipSession session,
String sessionDescription) {
if (mListener != null) {
mListener.onCallEstablished(SipSession.this,
sessionDescription);
}
}
public void onCallEnded(ISipSession session) {
if (mListener != null) {
mListener.onCallEnded(SipSession.this);
}
}
public void onCallBusy(ISipSession session) {
if (mListener != null) {
mListener.onCallBusy(SipSession.this);
}
}
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
mListener.onCallChangeFailed(SipSession.this, errorCode,
message);
}
}
public void onError(ISipSession session, int errorCode, String message) {
if (mListener != null) {
mListener.onError(SipSession.this, errorCode, message);
}
}
public void onRegistering(ISipSession session) {
if (mListener != null) {
mListener.onRegistering(SipSession.this);
}
}
public void onRegistrationDone(ISipSession session, int duration) {
if (mListener != null) {
mListener.onRegistrationDone(SipSession.this, duration);
}
}
public void onRegistrationFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
mListener.onRegistrationFailed(SipSession.this, errorCode,
message);
}
}
public void onRegistrationTimeout(ISipSession session) {
if (mListener != null) {
mListener.onRegistrationTimeout(SipSession.this);
}
}
};
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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 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) {
}
}

View File

@@ -0,0 +1,39 @@
<HTML>
<BODY>
<p>Provides access to Session Initiation Protocol (SIP) functionality, such as
making and answering VOIP calls using SIP.</p>
<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.<br/><br/>
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>

View File

@@ -0,0 +1,471 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sip;
import gov.nist.javax.sip.SipStackExt;
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
import android.net.sip.SipProfile;
import android.util.Log;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.DialogTerminatedEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.Transaction;
import javax.sip.TransactionAlreadyExistsException;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.TransactionState;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.Header;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Message;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
/**
* Helper class for holding SIP stack related classes and for various low-level
* SIP tasks like sending messages.
*/
class SipHelper {
private static final String TAG = SipHelper.class.getSimpleName();
private static final boolean DEBUG = true;
private SipStack mSipStack;
private SipProvider mSipProvider;
private AddressFactory mAddressFactory;
private HeaderFactory mHeaderFactory;
private MessageFactory mMessageFactory;
public SipHelper(SipStack sipStack, SipProvider sipProvider)
throws PeerUnavailableException {
mSipStack = sipStack;
mSipProvider = sipProvider;
SipFactory sipFactory = SipFactory.getInstance();
mAddressFactory = sipFactory.createAddressFactory();
mHeaderFactory = sipFactory.createHeaderFactory();
mMessageFactory = sipFactory.createMessageFactory();
}
private FromHeader createFromHeader(SipProfile profile, String tag)
throws ParseException {
return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
}
private ToHeader createToHeader(SipProfile profile) throws ParseException {
return createToHeader(profile, null);
}
private ToHeader createToHeader(SipProfile profile, String tag)
throws ParseException {
return mHeaderFactory.createToHeader(profile.getSipAddress(), tag);
}
private CallIdHeader createCallIdHeader() {
return mSipProvider.getNewCallId();
}
private CSeqHeader createCSeqHeader(String method)
throws ParseException, InvalidArgumentException {
long sequence = (long) (Math.random() * 10000);
return mHeaderFactory.createCSeqHeader(sequence, method);
}
private MaxForwardsHeader createMaxForwardsHeader()
throws InvalidArgumentException {
return mHeaderFactory.createMaxForwardsHeader(70);
}
private MaxForwardsHeader createMaxForwardsHeader(int max)
throws InvalidArgumentException {
return mHeaderFactory.createMaxForwardsHeader(max);
}
private ListeningPoint getListeningPoint() throws SipException {
ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP);
if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP);
if (lp == null) {
ListeningPoint[] lps = mSipProvider.getListeningPoints();
if ((lps != null) && (lps.length > 0)) lp = lps[0];
}
if (lp == null) {
throw new SipException("no listening point is available");
}
return lp;
}
private List<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 {
ListeningPoint lp = getListeningPoint();
SipURI contactURI =
createSipUri(profile.getUserName(), profile.getProtocol(), lp);
Address contactAddress = mAddressFactory.createAddress(contactURI);
contactAddress.setDisplayName(profile.getDisplayName());
return mHeaderFactory.createContactHeader(contactAddress);
}
private ContactHeader createWildcardContactHeader() {
ContactHeader contactHeader = mHeaderFactory.createContactHeader();
contactHeader.setWildCard();
return contactHeader;
}
private SipURI createSipUri(String username, String transport,
ListeningPoint lp) throws ParseException {
SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress());
try {
uri.setPort(lp.getPort());
uri.setTransportParam(transport);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
}
return uri;
}
public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag)
throws SipException {
try {
Request request = createRequest(Request.OPTIONS, userProfile, tag);
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (Exception e) {
throw new SipException("sendKeepAlive()", e);
}
}
public ClientTransaction sendRegister(SipProfile userProfile, String tag,
int expiry) throws SipException {
try {
Request request = createRequest(Request.REGISTER, userProfile, tag);
if (expiry == 0) {
// remove all previous registrations by wildcard
// rfc3261#section-10.2.2
request.addHeader(createWildcardContactHeader());
} else {
request.addHeader(createContactHeader(userProfile));
}
request.addHeader(mHeaderFactory.createExpiresHeader(expiry));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendRegister()", e);
}
}
private Request createRequest(String requestType, SipProfile userProfile,
String tag) throws ParseException, SipException {
FromHeader fromHeader = createFromHeader(userProfile, tag);
ToHeader toHeader = createToHeader(userProfile);
SipURI requestURI = mAddressFactory.createSipURI(
userProfile.getUriString().replaceFirst(
userProfile.getUserName() + "@", ""));
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 (DEBUG) Log.d(TAG, "send request with challenge response: "
+ ct.getRequest());
ct.sendRequest();
return ct;
}
public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
String sessionDescription, String tag)
throws SipException {
try {
FromHeader fromHeader = createFromHeader(caller, tag);
ToHeader toHeader = createToHeader(callee);
SipURI requestURI = callee.getUri();
List<ViaHeader> viaHeaders = createViaHeaders();
CallIdHeader callIdHeader = createCallIdHeader();
CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE);
MaxForwardsHeader maxForwards = createMaxForwardsHeader();
Request request = mMessageFactory.createRequest(requestURI,
Request.INVITE, callIdHeader, cSeqHeader, fromHeader,
toHeader, viaHeaders, maxForwards);
request.addHeader(createContactHeader(caller));
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
if (DEBUG) Log.d(TAG, "send INVITE: " + request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendInvite()", e);
}
}
public ClientTransaction sendReinvite(Dialog dialog,
String sessionDescription) throws SipException {
try {
Request request = dialog.createRequest(Request.INVITE);
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
if (DEBUG) Log.d(TAG, "send RE-INVITE: " + request);
dialog.sendRequest(clientTransaction);
return clientTransaction;
} catch (ParseException e) {
throw new SipException("sendReinvite()", e);
}
}
private ServerTransaction getServerTransaction(RequestEvent event)
throws SipException {
ServerTransaction transaction = event.getServerTransaction();
if (transaction == null) {
Request request = event.getRequest();
return mSipProvider.getNewServerTransaction(request);
} else {
return transaction;
}
}
/**
* @param event the INVITE request event
*/
public ServerTransaction sendRinging(RequestEvent event, String tag)
throws SipException {
try {
Request request = event.getRequest();
ServerTransaction transaction = getServerTransaction(event);
Response response = mMessageFactory.createResponse(Response.RINGING,
request);
ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
toHeader.setTag(tag);
response.addHeader(toHeader);
if (DEBUG) Log.d(TAG, "send RINGING: " + response);
transaction.sendResponse(response);
return transaction;
} catch (ParseException e) {
throw new SipException("sendRinging()", e);
}
}
/**
* @param event the INVITE request event
*/
public ServerTransaction sendInviteOk(RequestEvent event,
SipProfile localProfile, String sessionDescription,
ServerTransaction inviteTransaction)
throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(Response.OK,
request);
response.addHeader(createContactHeader(localProfile));
response.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
if (inviteTransaction == null) {
inviteTransaction = getServerTransaction(event);
}
if (inviteTransaction.getState() != TransactionState.COMPLETED) {
if (DEBUG) Log.d(TAG, "send OK: " + response);
inviteTransaction.sendResponse(response);
}
return inviteTransaction;
} catch (ParseException e) {
throw new SipException("sendInviteOk()", e);
}
}
public void sendInviteBusyHere(RequestEvent event,
ServerTransaction inviteTransaction) throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(
Response.BUSY_HERE, request);
if (inviteTransaction == null) {
inviteTransaction = getServerTransaction(event);
}
if (inviteTransaction.getState() != TransactionState.COMPLETED) {
if (DEBUG) Log.d(TAG, "send BUSY HERE: " + response);
inviteTransaction.sendResponse(response);
}
} catch (ParseException e) {
throw new SipException("sendInviteBusyHere()", e);
}
}
/**
* @param event the INVITE ACK request event
*/
public void sendInviteAck(ResponseEvent event, Dialog dialog)
throws SipException {
Response response = event.getResponse();
long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
.getSeqNumber();
Request ack = dialog.createAck(cseq);
if (DEBUG) Log.d(TAG, "send ACK: " + ack);
dialog.sendAck(ack);
}
public void sendBye(Dialog dialog) throws SipException {
Request byeRequest = dialog.createRequest(Request.BYE);
if (DEBUG) Log.d(TAG, "send BYE: " + byeRequest);
dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest));
}
public void sendCancel(ClientTransaction inviteTransaction)
throws SipException {
Request cancelRequest = inviteTransaction.createCancel();
if (DEBUG) Log.d(TAG, "send CANCEL: " + cancelRequest);
mSipProvider.getNewClientTransaction(cancelRequest).sendRequest();
}
public void sendResponse(RequestEvent event, int responseCode)
throws SipException {
try {
Response response = mMessageFactory.createResponse(
responseCode, event.getRequest());
if (DEBUG) Log.d(TAG, "send response: " + response);
getServerTransaction(event).sendResponse(response);
} catch (ParseException e) {
throw new SipException("sendResponse()", e);
}
}
public void sendInviteRequestTerminated(Request inviteRequest,
ServerTransaction inviteTransaction) throws SipException {
try {
Response response = mMessageFactory.createResponse(
Response.REQUEST_TERMINATED, inviteRequest);
if (DEBUG) Log.d(TAG, "send response: " + response);
inviteTransaction.sendResponse(response);
} catch (ParseException e) {
throw new SipException("sendInviteRequestTerminated()", e);
}
}
public static String getCallId(EventObject event) {
if (event == null) return null;
if (event instanceof RequestEvent) {
return getCallId(((RequestEvent) event).getRequest());
} else if (event instanceof ResponseEvent) {
return getCallId(((ResponseEvent) event).getResponse());
} else if (event instanceof DialogTerminatedEvent) {
Dialog dialog = ((DialogTerminatedEvent) event).getDialog();
return getCallId(((DialogTerminatedEvent) event).getDialog());
} else if (event instanceof TransactionTerminatedEvent) {
TransactionTerminatedEvent e = (TransactionTerminatedEvent) event;
return getCallId(e.isServerTransaction()
? e.getServerTransaction()
: e.getClientTransaction());
} else {
Object source = event.getSource();
if (source instanceof Transaction) {
return getCallId(((Transaction) source));
} else if (source instanceof Dialog) {
return getCallId((Dialog) source);
}
}
return "";
}
public static String getCallId(Transaction transaction) {
return ((transaction != null) ? getCallId(transaction.getRequest())
: "");
}
private static String getCallId(Message message) {
CallIdHeader callIdHeader =
(CallIdHeader) message.getHeader(CallIdHeader.NAME);
return callIdHeader.getCallId();
}
private static String getCallId(Dialog dialog) {
return dialog.getCallId().getCallId();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sip;
import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener;
import android.net.sip.SipProfile;
import android.os.DeadObjectException;
import android.util.Log;
/** Class to help safely run a callback in a different thread. */
class SipSessionListenerProxy extends ISipSessionListener.Stub {
private static final String TAG = "SipSession";
private ISipSessionListener mListener;
public void setListener(ISipSessionListener listener) {
mListener = listener;
}
public ISipSessionListener getListener() {
return mListener;
}
private void proxy(Runnable runnable) {
// One thread for each calling back.
// Note: Guarantee ordering if the issue becomes important. Currently,
// the chance of handling two callback events at a time is none.
new Thread(runnable, "SipSessionCallbackThread").start();
}
public void onCalling(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onCalling(session);
} catch (Throwable t) {
handle(t, "onCalling()");
}
}
});
}
public void onRinging(final ISipSession session, final SipProfile caller,
final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRinging(session, caller, sessionDescription);
} catch (Throwable t) {
handle(t, "onRinging()");
}
}
});
}
public void onRingingBack(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRingingBack(session);
} catch (Throwable t) {
handle(t, "onRingingBack()");
}
}
});
}
public void onCallEstablished(final ISipSession session,
final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onCallEstablished(session, sessionDescription);
} catch (Throwable t) {
handle(t, "onCallEstablished()");
}
}
});
}
public void onCallEnded(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onCallEnded(session);
} catch (Throwable t) {
handle(t, "onCallEnded()");
}
}
});
}
public void onCallBusy(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onCallBusy(session);
} catch (Throwable t) {
handle(t, "onCallBusy()");
}
}
});
}
public void onCallChangeFailed(final ISipSession session,
final int errorCode, final String message) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onCallChangeFailed(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onCallChangeFailed()");
}
}
});
}
public void onError(final ISipSession session, final int errorCode,
final String message) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onError(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onError()");
}
}
});
}
public void onRegistering(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRegistering(session);
} catch (Throwable t) {
handle(t, "onRegistering()");
}
}
});
}
public void onRegistrationDone(final ISipSession session,
final int duration) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRegistrationDone(session, duration);
} catch (Throwable t) {
handle(t, "onRegistrationDone()");
}
}
});
}
public void onRegistrationFailed(final ISipSession session,
final int errorCode, final String message) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRegistrationFailed(session, errorCode, message);
} catch (Throwable t) {
handle(t, "onRegistrationFailed()");
}
}
});
}
public void onRegistrationTimeout(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
try {
mListener.onRegistrationTimeout(session);
} catch (Throwable t) {
handle(t, "onRegistrationTimeout()");
}
}
});
}
private void handle(Throwable t, String message) {
if (t instanceof DeadObjectException) {
mListener = null;
// This creates race but it's harmless. Just don't log the error
// when it happens.
} else if (mListener != null) {
Log.w(TAG, message, t);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2010, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sip;
import android.os.PowerManager;
import android.util.Log;
import java.util.HashSet;
class SipWakeLock {
private static final boolean DEBUGV = SipService.DEBUGV;
private static final String TAG = SipService.TAG;
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock;
private PowerManager.WakeLock mTimerWakeLock;
private HashSet<Object> mHolders = new HashSet<Object>();
SipWakeLock(PowerManager powerManager) {
mPowerManager = powerManager;
}
synchronized void reset() {
mHolders.clear();
release(null);
if (DEBUGV) Log.v(TAG, "~~~ hard reset wakelock");
}
synchronized void acquire(long timeout) {
if (mTimerWakeLock == null) {
mTimerWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock.timer");
mTimerWakeLock.setReferenceCounted(true);
}
mTimerWakeLock.acquire(timeout);
}
synchronized void acquire(Object holder) {
mHolders.add(holder);
if (mWakeLock == null) {
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "SipWakeLock");
}
if (!mWakeLock.isHeld()) mWakeLock.acquire();
if (DEBUGV) Log.v(TAG, "acquire wakelock: holder count="
+ mHolders.size());
}
synchronized void release(Object holder) {
mHolders.remove(holder);
if ((mWakeLock != null) && mHolders.isEmpty()
&& mWakeLock.isHeld()) {
mWakeLock.release();
}
if (DEBUGV) Log.v(TAG, "release wakelock: holder count="
+ mHolders.size());
}
}