| /* |
| * 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; |
| } |
| } |