blob: a33263f526121ffacf2a8a3199e8af63d8dfa863 [file] [log] [blame]
/*
* Copyright (C) 2007 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.bluetooth;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.FileDescriptor;
/**
* The Android Bluetooth API is not finalized, and *will* change. Use at your
* own risk.
*
* This class implements an API to the Bluetooth RFCOMM layer. An RFCOMM socket
* is similar to a normal socket in that it takes an address and a port number.
* The difference is of course that the address is a Bluetooth-device address,
* and the port number is an RFCOMM channel. The API allows for the
* establishment of listening sockets via methods
* {@link #bind(String, int) bind}, {@link #listen(int) listen}, and
* {@link #accept(RfcommSocket, int) accept}, as well as for the making of
* outgoing connections with {@link #connect(String, int) connect},
* {@link #connectAsync(String, int) connectAsync}, and
* {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
*
* After constructing a socket, you need to {@link #create() create} it and then
* {@link #destroy() destroy} it when you are done using it. Both
* {@link #create() create} and {@link #accept(RfcommSocket, int) accept} return
* a {@link java.io.FileDescriptor FileDescriptor} for the actual data.
* Alternatively, you may call {@link #getInputStream() getInputStream} and
* {@link #getOutputStream() getOutputStream} to retrieve the respective streams
* without going through the FileDescriptor.
*
* @hide
*/
public class RfcommSocket {
/**
* Used by the native implementation of the class.
*/
private int mNativeData;
/**
* Used by the native implementation of the class.
*/
private int mPort;
/**
* Used by the native implementation of the class.
*/
private String mAddress;
/**
* We save the return value of {@link #create() create} and
* {@link #accept(RfcommSocket,int) accept} in this variable, and use it to
* retrieve the I/O streams.
*/
private FileDescriptor mFd;
/**
* After a call to {@link #waitForAsyncConnect(int) waitForAsyncConnect},
* if the return value is zero, then, the the remaining time left to wait is
* written into this variable (by the native implementation). It is possible
* that {@link #waitForAsyncConnect(int) waitForAsyncConnect} returns before
* the user-specified timeout expires, which is why we save the remaining
* time in this member variable for the user to retrieve by calling method
* {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}.
*/
private int mTimeoutRemainingMs;
/**
* Set to true when an asynchronous (nonblocking) connect is in progress.
* {@see #connectAsync(String,int)}.
*/
private boolean mIsConnecting;
/**
* Set to true after a successful call to {@link #bind(String,int) bind} and
* used for error checking in {@link #listen(int) listen}. Reset to false
* on {@link #destroy() destroy}.
*/
private boolean mIsBound = false;
/**
* Set to true after a successful call to {@link #listen(int) listen} and
* used for error checking in {@link #accept(RfcommSocket,int) accept}.
* Reset to false on {@link #destroy() destroy}.
*/
private boolean mIsListening = false;
/**
* Used to store the remaining time after an accept with a non-negative
* timeout returns unsuccessfully. It is possible that a blocking
* {@link #accept(int) accept} may wait for less than the time specified by
* the user, which is why we store the remainder in this member variable for
* it to be retrieved with method
* {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}.
*/
private int mAcceptTimeoutRemainingMs;
/**
* Maintained by {@link #getInputStream() getInputStream}.
*/
protected FileInputStream mInputStream;
/**
* Maintained by {@link #getOutputStream() getOutputStream}.
*/
protected FileOutputStream mOutputStream;
private native void initializeNativeDataNative();
/**
* Constructor.
*/
public RfcommSocket() {
initializeNativeDataNative();
}
private native void cleanupNativeDataNative();
/**
* Called by the GC to clean up the native data that we set up when we
* construct the object.
*/
protected void finalize() throws Throwable {
try {
cleanupNativeDataNative();
} finally {
super.finalize();
}
}
private native static void classInitNative();
static {
classInitNative();
}
/**
* Creates a socket. You need to call this method before performing any
* other operation on a socket.
*
* @return FileDescriptor for the data stream.
* @throws IOException
* @see #destroy()
*/
public FileDescriptor create() throws IOException {
if (mFd == null) {
mFd = createNative();
}
if (mFd == null) {
throw new IOException("socket not created");
}
return mFd;
}
private native FileDescriptor createNative();
/**
* Destroys a socket created by {@link #create() create}. Call this
* function when you no longer use the socket in order to release the
* underlying OS resources.
*
* @see #create()
*/
public void destroy() {
synchronized (this) {
destroyNative();
mFd = null;
mIsBound = false;
mIsListening = false;
}
}
private native void destroyNative();
/**
* Returns the {@link java.io.FileDescriptor FileDescriptor} of the socket.
*
* @return the FileDescriptor
* @throws IOException
* when the socket has not been {@link #create() created}.
*/
public FileDescriptor getFileDescriptor() throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
return mFd;
}
/**
* Retrieves the input stream from the socket. Alternatively, you can do
* that from the FileDescriptor returned by {@link #create() create} or
* {@link #accept(RfcommSocket, int) accept}.
*
* @return InputStream
* @throws IOException
* if you have not called {@link #create() create} on the
* socket.
*/
public InputStream getInputStream() throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
synchronized (this) {
if (mInputStream == null) {
mInputStream = new FileInputStream(mFd);
}
return mInputStream;
}
}
/**
* Retrieves the output stream from the socket. Alternatively, you can do
* that from the FileDescriptor returned by {@link #create() create} or
* {@link #accept(RfcommSocket, int) accept}.
*
* @return OutputStream
* @throws IOException
* if you have not called {@link #create() create} on the
* socket.
*/
public OutputStream getOutputStream() throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
synchronized (this) {
if (mOutputStream == null) {
mOutputStream = new FileOutputStream(mFd);
}
return mOutputStream;
}
}
/**
* Starts a blocking connect to a remote RFCOMM socket. It takes the address
* of a device and the RFCOMM channel (port) to which to connect.
*
* @param address
* is the Bluetooth address of the remote device.
* @param port
* is the RFCOMM channel
* @return true on success, false on failure
* @throws IOException
* if {@link #create() create} has not been called.
* @see #connectAsync(String, int)
*/
public boolean connect(String address, int port) throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
return connectNative(address, port);
}
}
private native boolean connectNative(String address, int port);
/**
* Starts an asynchronous (nonblocking) connect to a remote RFCOMM socket.
* It takes the address of the device to connect to, as well as the RFCOMM
* channel (port). On successful return (return value is true), you need to
* call method {@link #waitForAsyncConnect(int) waitForAsyncConnect} to
* block for up to a specified number of milliseconds while waiting for the
* asyncronous connect to complete.
*
* @param address
* of remote device
* @param port
* the RFCOMM channel
* @return true when the asynchronous connect has successfully started,
* false if there was an error.
* @throws IOException
* is you have not called {@link #create() create}
* @see #waitForAsyncConnect(int)
* @see #getRemainingAsyncConnectWaitingTimeMs()
* @see #connect(String, int)
*/
public boolean connectAsync(String address, int port) throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
mIsConnecting = connectAsyncNative(address, port);
return mIsConnecting;
}
}
private native boolean connectAsyncNative(String address, int port);
/**
* Interrupts an asynchronous connect in progress. This method does nothing
* when there is no asynchronous connect in progress.
*
* @throws IOException
* if you have not called {@link #create() create}.
* @see #connectAsync(String, int)
*/
public void interruptAsyncConnect() throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
if (mIsConnecting) {
mIsConnecting = !interruptAsyncConnectNative();
}
}
}
private native boolean interruptAsyncConnectNative();
/**
* Tells you whether there is an asynchronous connect in progress. This
* method returns an undefined value when there is a synchronous connect in
* progress.
*
* @return true if there is an asyc connect in progress, false otherwise
* @see #connectAsync(String, int)
*/
public boolean isConnecting() {
return mIsConnecting;
}
/**
* Blocks for a specified amount of milliseconds while waiting for an
* asynchronous connect to complete. Returns an integer value to indicate
* one of the following: the connect succeeded, the connect is still in
* progress, or the connect failed. It is possible for this method to block
* for less than the time specified by the user, and still return zero
* (i.e., async connect is still in progress.) For this reason, if the
* return value is zero, you need to call method
* {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}
* to retrieve the remaining time.
*
* @param timeoutMs
* the time to block while waiting for the async connect to
* complete.
* @return a positive value if the connect succeeds; zero, if the connect is
* still in progress, and a negative value if the connect failed.
*
* @throws IOException
* @see #getRemainingAsyncConnectWaitingTimeMs()
* @see #connectAsync(String, int)
*/
public int waitForAsyncConnect(int timeoutMs) throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
int ret = waitForAsyncConnectNative(timeoutMs);
if (ret != 0) {
mIsConnecting = false;
}
return ret;
}
}
private native int waitForAsyncConnectNative(int timeoutMs);
/**
* Returns the number of milliseconds left to wait after the last call to
* {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
*
* It is possible that waitForAsyncConnect() waits for less than the time
* specified by the user, and still returns zero (i.e., async connect is
* still in progress.) For this reason, if the return value is zero, you
* need to call this method to retrieve the remaining time before you call
* waitForAsyncConnect again.
*
* @return the remaining timeout in milliseconds.
* @see #waitForAsyncConnect(int)
* @see #connectAsync(String, int)
*/
public int getRemainingAsyncConnectWaitingTimeMs() {
return mTimeoutRemainingMs;
}
/**
* Shuts down both directions on a socket.
*
* @return true on success, false on failure; if the return value is false,
* the socket might be left in a patially shut-down state (i.e. one
* direction is shut down, but the other is still open.) In this
* case, you should {@link #destroy() destroy} and then
* {@link #create() create} the socket again.
* @throws IOException
* is you have not caled {@link #create() create}.
* @see #shutdownInput()
* @see #shutdownOutput()
*/
public boolean shutdown() throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
if (shutdownNative(true)) {
return shutdownNative(false);
}
return false;
}
}
/**
* Shuts down the input stream of the socket, but leaves the output stream
* in its current state.
*
* @return true on success, false on failure
* @throws IOException
* is you have not called {@link #create() create}
* @see #shutdown()
* @see #shutdownOutput()
*/
public boolean shutdownInput() throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
return shutdownNative(true);
}
}
/**
* Shut down the output stream of the socket, but leaves the input stream in
* its current state.
*
* @return true on success, false on failure
* @throws IOException
* is you have not called {@link #create() create}
* @see #shutdown()
* @see #shutdownInput()
*/
public boolean shutdownOutput() throws IOException {
synchronized (this) {
if (mFd == null) {
throw new IOException("socket not created");
}
return shutdownNative(false);
}
}
private native boolean shutdownNative(boolean shutdownInput);
/**
* Tells you whether a socket is connected to another socket. This could be
* for input or output or both.
*
* @return true if connected, false otherwise.
* @see #isInputConnected()
* @see #isOutputConnected()
*/
public boolean isConnected() {
return isConnectedNative() > 0;
}
/**
* Determines whether input is connected (i.e., whether you can receive data
* on this socket.)
*
* @return true if input is connected, false otherwise.
* @see #isConnected()
* @see #isOutputConnected()
*/
public boolean isInputConnected() {
return (isConnectedNative() & 1) != 0;
}
/**
* Determines whether output is connected (i.e., whether you can send data
* on this socket.)
*
* @return true if output is connected, false otherwise.
* @see #isConnected()
* @see #isInputConnected()
*/
public boolean isOutputConnected() {
return (isConnectedNative() & 2) != 0;
}
private native int isConnectedNative();
/**
* Binds a listening socket to the local device, or a non-listening socket
* to a remote device. The port is automatically selected as the first
* available port in the range 12 to 30.
*
* NOTE: Currently we ignore the device parameter and always bind the socket
* to the local device, assuming that it is a listening socket.
*
* TODO: Use bind(0) in native code to have the kernel select an unused
* port.
*
* @param device
* Bluetooth address of device to bind to (currently ignored).
* @return true on success, false on failure
* @throws IOException
* if you have not called {@link #create() create}
* @see #listen(int)
* @see #accept(RfcommSocket,int)
*/
public boolean bind(String device) throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
for (int port = 12; port <= 30; port++) {
if (bindNative(device, port)) {
mIsBound = true;
return true;
}
}
mIsBound = false;
return false;
}
/**
* Binds a listening socket to the local device, or a non-listening socket
* to a remote device.
*
* NOTE: Currently we ignore the device parameter and always bind the socket
* to the local device, assuming that it is a listening socket.
*
* @param device
* Bluetooth address of device to bind to (currently ignored).
* @param port
* RFCOMM channel to bind socket to.
* @return true on success, false on failure
* @throws IOException
* if you have not called {@link #create() create}
* @see #listen(int)
* @see #accept(RfcommSocket,int)
*/
public boolean bind(String device, int port) throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
mIsBound = bindNative(device, port);
return mIsBound;
}
private native boolean bindNative(String device, int port);
/**
* Starts listening for incoming connections on this socket, after it has
* been bound to an address and RFCOMM channel with
* {@link #bind(String,int) bind}.
*
* @param backlog
* the number of pending incoming connections to queue for
* {@link #accept(RfcommSocket, int) accept}.
* @return true on success, false on failure
* @throws IOException
* if you have not called {@link #create() create} or if the
* socket has not been bound to a device and RFCOMM channel.
*/
public boolean listen(int backlog) throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
if (!mIsBound) {
throw new IOException("socket not bound");
}
mIsListening = listenNative(backlog);
return mIsListening;
}
private native boolean listenNative(int backlog);
/**
* Accepts incoming-connection requests for a listening socket bound to an
* RFCOMM channel. The user may provide a time to wait for an incoming
* connection.
*
* Note that this method may return null (i.e., no incoming connection)
* before the user-specified timeout expires. For this reason, on a null
* return value, you need to call
* {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}
* in order to see how much time is left to wait, before you call this
* method again.
*
* @param newSock
* is set to the new socket that is created as a result of a
* successful accept.
* @param timeoutMs
* time (in milliseconds) to block while waiting to an
* incoming-connection request. A negative value is an infinite
* wait.
* @return FileDescriptor of newSock on success, null on failure. Failure
* occurs if the timeout expires without a successful connect.
* @throws IOException
* if the socket has not been {@link #create() create}ed, is
* not bound, or is not a listening socket.
* @see #bind(String, int)
* @see #listen(int)
* @see #getRemainingAcceptWaitingTimeMs()
*/
public FileDescriptor accept(RfcommSocket newSock, int timeoutMs)
throws IOException {
synchronized (newSock) {
if (mFd == null) {
throw new IOException("socket not created");
}
if (mIsListening == false) {
throw new IOException("not listening on socket");
}
newSock.mFd = acceptNative(newSock, timeoutMs);
return newSock.mFd;
}
}
/**
* Returns the number of milliseconds left to wait after the last call to
* {@link #accept(RfcommSocket, int) accept}.
*
* Since accept() may return null (i.e., no incoming connection) before the
* user-specified timeout expires, you need to call this method in order to
* see how much time is left to wait, and wait for that amount of time
* before you call accept again.
*
* @return the remaining time, in milliseconds.
*/
public int getRemainingAcceptWaitingTimeMs() {
return mAcceptTimeoutRemainingMs;
}
private native FileDescriptor acceptNative(RfcommSocket newSock,
int timeoutMs);
/**
* Get the port (rfcomm channel) associated with this socket.
*
* This is only valid if the port has been set via a successful call to
* {@link #bind(String, int)}, {@link #connect(String, int)}
* or {@link #connectAsync(String, int)}. This can be checked
* with {@link #isListening()} and {@link #isConnected()}.
* @return Port (rfcomm channel)
*/
public int getPort() throws IOException {
if (mFd == null) {
throw new IOException("socket not created");
}
if (!mIsListening && !isConnected()) {
throw new IOException("not listening or connected on socket");
}
return mPort;
}
/**
* Return true if this socket is listening ({@link #listen(int)}
* has been called successfully).
*/
public boolean isListening() {
return mIsListening;
}
}