Merge change 2007

* changes:
  New BluetoothSocket API.
This commit is contained in:
Android (Google) Code Review
2009-05-26 19:47:08 -07:00
11 changed files with 750 additions and 1301 deletions

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2009 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.InputStream;
/**
* BluetoothInputStream.
*
* Used to write to a Bluetooth socket.
*
* TODO: Implement bulk writes (instead of one byte at a time).
* @hide
*/
/*package*/ final class BluetoothInputStream extends InputStream {
private BluetoothSocket mSocket;
/*package*/ BluetoothInputStream(BluetoothSocket s) {
mSocket = s;
}
/**
* Return number of bytes available before this stream will block.
*/
public int available() throws IOException {
return mSocket.availableNative();
}
public void close() throws IOException {
mSocket.close();
}
/**
* Reads a single byte from this stream and returns it as an integer in the
* range from 0 to 255. Returns -1 if the end of the stream has been
* reached. Blocks until one byte has been read, the end of the source
* stream is detected or an exception is thrown.
*
* @return the byte read or -1 if the end of stream has been reached.
* @throws IOException
* if the stream is closed or another IOException occurs.
* @since Android 1.0
*/
public int read() throws IOException {
return mSocket.readNative();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2009 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.OutputStream;
/**
* BluetoothOutputStream.
*
* Used to read from a Bluetooth socket.
*
* TODO: Implement bulk reads (instead of one byte at a time).
* @hide
*/
/*package*/ final class BluetoothOutputStream extends OutputStream {
private BluetoothSocket mSocket;
/*package*/ BluetoothOutputStream(BluetoothSocket s) {
mSocket = s;
}
/**
* Close this output stream and the socket associated with it.
*/
public void close() throws IOException {
mSocket.close();
}
/**
* Writes a single byte to this stream. Only the least significant byte of
* the integer {@code oneByte} is written to the stream.
*
* @param oneByte
* the byte to be written.
* @throws IOException
* if an error occurs while writing to this stream.
* @since Android 1.0
*/
public void write(int oneByte) throws IOException {
mSocket.writeNative(oneByte);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2009 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.Closeable;
import java.io.IOException;
/**
* Server (listening) Bluetooth Socket.
*
* Currently only supports RFCOMM sockets.
*
* RFCOMM is a connection orientated, streaming transport over Bluetooth. It is
* also known as the Serial Port Profile (SPP).
*
* TODO: Consider implementing SCO and L2CAP sockets.
* TODO: Clean up javadoc grammer and formatting.
* TODO: Remove @hide
* @hide
*/
public final class BluetoothServerSocket implements Closeable {
private final BluetoothSocket mSocket;
/**
* Construct a listening, secure RFCOMM server socket.
* The remote device connecting to this socket will be authenticated and
* communication on this socket will be encrypted.
* Call #accept to retrieve connections to this socket.
* @return An RFCOMM BluetoothServerSocket
* @throws IOException On error, for example Bluetooth not available, or
* insufficient permissions.
*/
public static BluetoothServerSocket listenUsingRfcommOn(int port) throws IOException {
BluetoothServerSocket socket = new BluetoothServerSocket(true, true);
try {
socket.mSocket.bindListenNative(port);
} catch (IOException e) {
try {
socket.close();
} catch (IOException e2) { }
throw e;
}
return socket;
}
/**
* Construct an unencrypted, unauthenticated, RFCOMM server socket.
* Call #accept to retrieve connections to this socket.
* @return An RFCOMM BluetoothServerSocket
* @throws IOException On error, for example Bluetooth not available, or
* insufficient permissions.
*/
public static BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
BluetoothServerSocket socket = new BluetoothServerSocket(false, false);
try {
socket.mSocket.bindListenNative(port);
} catch (IOException e) {
try {
socket.close();
} catch (IOException e2) { }
throw e;
}
return socket;
}
/**
* Construct a socket for incoming connections.
* @param auth Require the remote device to be authenticated
* @param encrypt Require the connection to be encrypted
* @throws IOException On error, for example Bluetooth not available, or
* insufficient priveleges
*/
private BluetoothServerSocket(boolean auth, boolean encrypt) throws IOException {
mSocket = new BluetoothSocket(-1, auth, encrypt, null, -1);
}
/**
* Block until a connection is established.
* Returns a connected #BluetoothSocket. This server socket can be reused
* for subsequent incoming connections by calling #accept repeatedly.
* #close can be used to abort this call from another thread.
* @return A connected #BluetoothSocket
* @throws IOException On error, for example this call was aborted
*/
public BluetoothSocket accept() throws IOException {
return accept(-1);
}
/**
* Block until a connection is established, with timeout.
* Returns a connected #BluetoothSocket. This server socket can be reused
* for subsequent incoming connections by calling #accept repeatedly.
* #close can be used to abort this call from another thread.
* @return A connected #BluetoothSocket
* @throws IOException On error, for example this call was aborted, or
* timeout
*/
public BluetoothSocket accept(int timeout) throws IOException {
return mSocket.acceptNative(timeout);
}
/**
* Closes this socket.
* This will cause other blocking calls on this socket to immediately
* throw an IOException.
*/
public void close() throws IOException {
mSocket.closeNative();
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright (C) 2009 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.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Represents a connected or connecting Bluetooth Socket.
*
* Currently only supports RFCOMM sockets.
*
* RFCOMM is a connection orientated, streaming transport over Bluetooth. It is
* also known as the Serial Port Profile (SPP).
*
* TODO: Consider implementing SCO and L2CAP sockets.
* TODO: Clean up javadoc grammer and formatting.
* TODO: Remove @hide
* @hide
*/
public final class BluetoothSocket implements Closeable {
private final int mPort;
private final String mAddress; /* remote address */
private final boolean mAuth;
private final boolean mEncrypt;
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
private int mSocketData; /* used by native code only */
/**
* Construct a secure RFCOMM socket ready to start an outgoing connection.
* Call #connect on the returned #BluetoothSocket to begin the connection.
* The remote device will be authenticated and communication on this socket
* will be encrypted.
* @param address remote Bluetooth address that this socket can connect to
* @param port remote port
* @return an RFCOMM BluetoothSocket
* @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions.
*/
public static BluetoothSocket createRfcommSocket(String address, int port)
throws IOException {
return new BluetoothSocket(-1, true, true, address, port);
}
/**
* Construct an insecure RFCOMM socket ready to start an outgoing
* connection.
* Call #connect on the returned #BluetoothSocket to begin the connection.
* The remote device will not be authenticated and communication on this
* socket will not be encrypted.
* @param address remote Bluetooth address that this socket can connect to
* @param port remote port
* @return An RFCOMM BluetoothSocket
* @throws IOException On error, for example Bluetooth not available, or
* insufficient permissions.
*/
public static BluetoothSocket createInsecureRfcommSocket(String address, int port)
throws IOException {
return new BluetoothSocket(-1, false, false, address, port);
}
/**
* Construct a Bluetooth.
* @param fd fd to use for connected socket, or -1 for a new socket
* @param auth require the remote device to be authenticated
* @param encrypt require the connection to be encrypted
* @param address remote Bluetooth address that this socket can connect to
* @param port remote port
* @throws IOException On error, for example Bluetooth not available, or
* insufficient priveleges
*/
/*package*/ BluetoothSocket(int fd, boolean auth, boolean encrypt, String address, int port)
throws IOException {
mAuth = auth;
mEncrypt = encrypt;
mAddress = address;
mPort = port;
if (fd == -1) {
initSocketNative();
} else {
initSocketFromFdNative(fd);
}
mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this);
}
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
/**
* Attempt to connect to a remote device.
* This method will block until a connection is made or the connection
* fails. If this method returns without an exception then this socket
* is now connected. #close can be used to abort this call from another
* thread.
* @throws IOException On error, for example connection failure
*/
public void connect() throws IOException {
connectNative(mAddress, mPort, -1);
}
/**
* Closes this socket.
* This will cause other blocking calls on this socket to immediately
* throw an IOException.
*/
public void close() throws IOException {
closeNative();
}
/**
* Return the address we are connecting, or connected, to.
* @return Bluetooth address, or null if this socket has not yet attempted
* or established a connection.
*/
public String getAddress() {
return mAddress;
}
/**
* Get the input stream associated with this socket.
* The input stream will be returned even if the socket is not yet
* connected, but operations on that stream will throw IOException until
* the associated socket is connected.
* @return InputStream
*/
public InputStream getInputStream() throws IOException {
return mInputStream;
}
/**
* Get the output stream associated with this socket.
* The output stream will be returned even if the socket is not yet
* connected, but operations on that stream will throw IOException until
* the associated socket is connected.
* @return OutputStream
*/
public OutputStream getOutputStream() throws IOException {
return mOutputStream;
}
private native void initSocketNative();
private native void initSocketFromFdNative(int fd);
private native void connectNative(String address, int port, int timeout);
/*package*/ native void bindListenNative(int port) throws IOException;
/*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException;
/*package*/ native int availableNative();
/*package*/ native int readNative();
/*package*/ native void writeNative(int data);
/*package*/ native void closeNative();
private native void destroyNative();
}

View File

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

View File

@@ -105,7 +105,7 @@ LOCAL_SRC_FILES:= \
android_bluetooth_HeadsetBase.cpp \
android_bluetooth_common.cpp \
android_bluetooth_BluetoothAudioGateway.cpp \
android_bluetooth_RfcommSocket.cpp \
android_bluetooth_BluetoothSocket.cpp \
android_bluetooth_ScoSocket.cpp \
android_server_BluetoothDeviceService.cpp \
android_server_BluetoothEventLoop.cpp \

View File

@@ -144,7 +144,7 @@ extern int register_android_text_KeyCharacterMap(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_bluetooth_HeadsetBase(JNIEnv* env);
extern int register_android_bluetooth_BluetoothAudioGateway(JNIEnv* env);
extern int register_android_bluetooth_RfcommSocket(JNIEnv *env);
extern int register_android_bluetooth_BluetoothSocket(JNIEnv *env);
extern int register_android_bluetooth_ScoSocket(JNIEnv *env);
extern int register_android_server_BluetoothDeviceService(JNIEnv* env);
extern int register_android_server_BluetoothEventLoop(JNIEnv *env);
@@ -1159,7 +1159,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_opengl_classes),
REG_JNI(register_android_bluetooth_HeadsetBase),
REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
REG_JNI(register_android_bluetooth_RfcommSocket),
REG_JNI(register_android_bluetooth_BluetoothSocket),
REG_JNI(register_android_bluetooth_ScoSocket),
REG_JNI(register_android_server_BluetoothDeviceService),
REG_JNI(register_android_server_BluetoothEventLoop),

View File

@@ -0,0 +1,324 @@
/*
* Copyright 2009, 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.
*/
#define LOG_TAG "BluetoothSocket.cpp"
#include "android_bluetooth_common.h"
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"
#include "utils/Log.h"
#include "cutils/abort_socket.h"
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef HAVE_BLUETOOTH
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#endif
namespace android {
static jfieldID field_mAuth; /* read-only */
static jfieldID field_mEncrypt; /* read-only */
static jfieldID field_mSocketData;
static jmethodID method_BluetoothSocket_ctor;
static jclass class_BluetoothSocket;
static struct asocket *get_socketData(JNIEnv *env, jobject obj) {
struct asocket *s =
(struct asocket *) env->GetIntField(obj, field_mSocketData);
if (!s)
jniThrowException(env, "java/io/IOException", "null socketData");
return s;
}
static void initSocketFromFdNative(JNIEnv *env, jobject obj, jint fd) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
struct asocket *s = asocket_init(fd);
if (!s) {
LOGV("asocket_init() failed, throwing");
jniThrowIOException(env, errno);
return;
}
env->SetIntField(obj, field_mSocketData, (jint)s);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static void initSocketNative(JNIEnv *env, jobject obj) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
int fd;
int lm = 0;
jboolean auth;
jboolean encrypt;
/*TODO: do not hardcode to rfcomm */
fd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (fd < 0) {
LOGV("socket() failed, throwing");
jniThrowIOException(env, errno);
return;
}
auth = env->GetBooleanField(obj, field_mAuth);
encrypt = env->GetBooleanField(obj, field_mEncrypt);
lm |= auth ? RFCOMM_LM_AUTH : 0;
lm |= encrypt? RFCOMM_LM_ENCRYPT : 0;
if (lm) {
if (setsockopt(fd, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm))) {
LOGV("setsockopt() failed, throwing");
jniThrowIOException(env, errno);
return;
}
}
initSocketFromFdNative(env, obj, fd);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static void connectNative(JNIEnv *env, jobject obj, jstring address,
jint port, jint timeout) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
int ret;
struct sockaddr_rc addr;
const char *c_address;
struct asocket *s = get_socketData(env, obj);
if (!s)
return;
addr.rc_family = AF_BLUETOOTH;
addr.rc_channel = port;
c_address = env->GetStringUTFChars(address, NULL);
if (get_bdaddr((const char *)c_address, &addr.rc_bdaddr)) {
env->ReleaseStringUTFChars(address, c_address);
jniThrowIOException(env, EINVAL);
return;
}
env->ReleaseStringUTFChars(address, c_address);
ret = asocket_connect(s, (struct sockaddr *)&addr, sizeof(addr), timeout);
if (ret)
jniThrowIOException(env, errno);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static void bindListenNative(JNIEnv *env, jobject obj, jint port) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
struct sockaddr_rc addr;
struct asocket *s = get_socketData(env, obj);
if (!s)
return;
memset(&addr, 0, sizeof(struct sockaddr_rc));
addr.rc_family = AF_BLUETOOTH;
addr.rc_bdaddr = *BDADDR_ANY;
addr.rc_channel = port;
if (bind(s->fd, (struct sockaddr *)&addr, sizeof(addr))) {
jniThrowIOException(env, errno);
return;
}
if (listen(s->fd, 1)) {
jniThrowIOException(env, errno);
return;
}
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static jobject acceptNative(JNIEnv *env, jobject obj, int timeout) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
int fd;
struct sockaddr_rc addr;
int addrlen = sizeof(addr);
jstring addr_jstr;
char addr_cstr[BTADDR_SIZE];
jboolean auth;
jboolean encrypt;
struct asocket *s = get_socketData(env, obj);
if (!s)
return NULL;
fd = asocket_accept(s, (struct sockaddr *)&addr, &addrlen, timeout);
if (fd < 0) {
jniThrowIOException(env, errno);
return NULL;
}
/* Connected - return new BluetoothSocket */
auth = env->GetBooleanField(obj, field_mAuth);
encrypt = env->GetBooleanField(obj, field_mEncrypt);
get_bdaddr_as_string(&addr.rc_bdaddr, addr_cstr);
addr_jstr = env->NewStringUTF(addr_cstr);
return env->NewObject(class_BluetoothSocket, method_BluetoothSocket_ctor, fd,
auth, encrypt, addr_jstr, -1);
#endif
jniThrowIOException(env, ENOSYS);
return NULL;
}
static jint availableNative(JNIEnv *env, jobject obj) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
int available;
struct asocket *s = get_socketData(env, obj);
if (!s)
return -1;
if (ioctl(s->fd, FIONREAD, &available) < 0) {
jniThrowIOException(env, errno);
return -1;
}
return available;
#endif
jniThrowIOException(env, ENOSYS);
return -1;
}
static jint readNative(JNIEnv *env, jobject obj) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
char buf;
struct asocket *s = get_socketData(env, obj);
if (!s)
return -1;
if (asocket_read(s, &buf, 1, -1) < 0) {
jniThrowIOException(env, errno);
return -1;
}
return (jint)buf;
#endif
jniThrowIOException(env, ENOSYS);
return -1;
}
static void writeNative(JNIEnv *env, jobject obj, jint data) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
const char buf = (char)data;
struct asocket *s = get_socketData(env, obj);
if (!s)
return;
if (asocket_write(s, &buf, 1, -1) < 0)
jniThrowIOException(env, errno);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static void closeNative(JNIEnv *env, jobject obj) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
struct asocket *s = get_socketData(env, obj);
if (!s)
return;
asocket_abort(s);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static void destroyNative(JNIEnv *env, jobject obj) {
#ifdef HAVE_BLUETOOTH
LOGV(__FUNCTION__);
struct asocket *s = get_socketData(env, obj);
if (!s)
return;
asocket_destroy(s);
return;
#endif
jniThrowIOException(env, ENOSYS);
}
static JNINativeMethod sMethods[] = {
{"initSocketNative", "()V", (void*) initSocketNative},
{"initSocketFromFdNative", "(I)V", (void*) initSocketFromFdNative},
{"connectNative", "(Ljava/lang/String;II)", (void *) connectNative},
{"bindListenNative", "(I)V", (void *) bindListenNative},
{"acceptNative", "(I)Landroid/bluetooth/BluetoothSocket;", (void *) acceptNative},
{"availableNative", "()I", (void *) availableNative},
{"readNative", "()I", (void *) readNative},
{"writeNative", "(I)V", (void *) writeNative},
{"closeNative", "()V", (void *) closeNative},
{"destroyNative", "()V", (void *) destroyNative},
};
int register_android_bluetooth_BluetoothSocket(JNIEnv *env) {
jclass clazz = env->FindClass("android/bluetooth/BluetoothSocket");
if (clazz == NULL)
return -1;
class_BluetoothSocket = (jclass) env->NewGlobalRef(clazz);
field_mAuth = env->GetFieldID(clazz, "mAuth", "Z");
field_mEncrypt = env->GetFieldID(clazz, "mEncrypt", "Z");
field_mSocketData = env->GetFieldID(clazz, "mSocketData", "I");
method_BluetoothSocket_ctor = env->GetMethodID(clazz, "<init>", "(IZZLjava/lang/String;I)V");
return AndroidRuntime::registerNativeMethods(env,
"android/bluetooth/BluetoothSocket", sMethods, NELEM(sMethods));
}
} /* namespace android */

View File

@@ -1,621 +0,0 @@
/*
** Copyright 2006, 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.
*/
#define LOG_TAG "bluetooth_RfcommSocket.cpp"
#include "android_bluetooth_common.h"
#include "android_runtime/AndroidRuntime.h"
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/poll.h>
#ifdef HAVE_BLUETOOTH
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sco.h>
#endif
namespace android {
#ifdef HAVE_BLUETOOTH
static jfieldID field_mNativeData;
static jfieldID field_mTimeoutRemainingMs;
static jfieldID field_mAcceptTimeoutRemainingMs;
static jfieldID field_mAddress;
static jfieldID field_mPort;
typedef struct {
jstring address;
const char *c_address;
int rfcomm_channel;
int last_read_err;
int rfcomm_sock;
// < 0 -- in progress,
// 0 -- not connected
// > 0 connected
// 1 input is open
// 2 output is open
// 3 both input and output are open
int rfcomm_connected;
int rfcomm_sock_flags;
} native_data_t;
static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
return (native_data_t *)(env->GetIntField(object, field_mNativeData));
}
static inline void init_socket_info(
JNIEnv *env, jobject object,
native_data_t *nat,
jstring address,
jint rfcomm_channel) {
nat->address = (jstring)env->NewGlobalRef(address);
nat->c_address = env->GetStringUTFChars(nat->address, NULL);
nat->rfcomm_channel = (int)rfcomm_channel;
}
static inline void cleanup_socket_info(JNIEnv *env, native_data_t *nat) {
if (nat->c_address != NULL) {
env->ReleaseStringUTFChars(nat->address, nat->c_address);
env->DeleteGlobalRef(nat->address);
nat->c_address = NULL;
}
}
#endif
static void classInitNative(JNIEnv* env, jclass clazz) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
field_mNativeData = get_field(env, clazz, "mNativeData", "I");
field_mTimeoutRemainingMs = get_field(env, clazz, "mTimeoutRemainingMs", "I");
field_mAcceptTimeoutRemainingMs = get_field(env, clazz, "mAcceptTimeoutRemainingMs", "I");
field_mAddress = get_field(env, clazz, "mAddress", "Ljava/lang/String;");
field_mPort = get_field(env, clazz, "mPort", "I");
#endif
}
static void initializeNativeDataNative(JNIEnv* env, jobject object) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = (native_data_t *)calloc(1, sizeof(native_data_t));
if (nat == NULL) {
LOGE("%s: out of memory!", __FUNCTION__);
return;
}
env->SetIntField(object, field_mNativeData, (jint)nat);
nat->rfcomm_sock = -1;
nat->rfcomm_connected = 0;
#endif
}
static void cleanupNativeDataNative(JNIEnv* env, jobject object) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, object);
if (nat) {
free(nat);
}
#endif
}
static jobject createNative(JNIEnv *env, jobject obj) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
int lm;
native_data_t *nat = get_native_data(env, obj);
nat->rfcomm_sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (nat->rfcomm_sock < 0) {
LOGE("%s: Could not create RFCOMM socket: %s\n", __FUNCTION__,
strerror(errno));
return NULL;
}
lm = RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT;
if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm,
sizeof(lm)) < 0) {
LOGE("%s: Can't set RFCOMM link mode", __FUNCTION__);
close(nat->rfcomm_sock);
return NULL;
}
return jniCreateFileDescriptor(env, nat->rfcomm_sock);
#else
return NULL;
#endif
}
static void destroyNative(JNIEnv *env, jobject obj) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, obj);
cleanup_socket_info(env, nat);
if (nat->rfcomm_sock >= 0) {
close(nat->rfcomm_sock);
nat->rfcomm_sock = -1;
}
#endif
}
static jboolean connectNative(JNIEnv *env, jobject obj,
jstring address, jint port) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock >= 0) {
if (nat->rfcomm_connected) {
LOGI("RFCOMM socket: %s.",
(nat->rfcomm_connected > 0) ? "already connected" : "connection is in progress");
return JNI_TRUE;
}
init_socket_info(env, obj, nat, address, port);
struct sockaddr_rc addr;
memset(&addr, 0, sizeof(struct sockaddr_rc));
get_bdaddr(nat->c_address, &addr.rc_bdaddr);
addr.rc_channel = nat->rfcomm_channel;
addr.rc_family = AF_BLUETOOTH;
nat->rfcomm_connected = 0;
while (nat->rfcomm_connected == 0) {
if (connect(nat->rfcomm_sock, (struct sockaddr *)&addr,
sizeof(addr)) < 0) {
if (errno == EINTR) continue;
LOGE("connect error: %s (%d)\n", strerror(errno), errno);
break;
} else {
nat->rfcomm_connected = 3; // input and output
}
}
} else {
LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
}
if (nat->rfcomm_connected > 0) {
env->SetIntField(obj, field_mPort, port);
return JNI_TRUE;
}
#endif
return JNI_FALSE;
}
static jboolean connectAsyncNative(JNIEnv *env, jobject obj,
jstring address, jint port) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock < 0) {
LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
return JNI_FALSE;
}
if (nat->rfcomm_connected) {
LOGI("RFCOMM socket: %s.",
(nat->rfcomm_connected > 0) ?
"already connected" : "connection is in progress");
return JNI_TRUE;
}
init_socket_info(env, obj, nat, address, port);
struct sockaddr_rc addr;
memset(&addr, 0, sizeof(struct sockaddr_rc));
get_bdaddr(nat->c_address, &addr.rc_bdaddr);
addr.rc_channel = nat->rfcomm_channel;
addr.rc_family = AF_BLUETOOTH;
nat->rfcomm_sock_flags = fcntl(nat->rfcomm_sock, F_GETFL, 0);
if (fcntl(nat->rfcomm_sock,
F_SETFL, nat->rfcomm_sock_flags | O_NONBLOCK) >= 0) {
int rc;
nat->rfcomm_connected = 0;
errno = 0;
rc = connect(nat->rfcomm_sock,
(struct sockaddr *)&addr,
sizeof(addr));
if (rc >= 0) {
nat->rfcomm_connected = 3;
LOGI("RFCOMM async connect immediately successful");
env->SetIntField(obj, field_mPort, port);
return JNI_TRUE;
}
else if (rc < 0) {
if (errno == EINPROGRESS || errno == EAGAIN)
{
LOGI("RFCOMM async connect is in progress (%s)",
strerror(errno));
nat->rfcomm_connected = -1;
env->SetIntField(obj, field_mPort, port);
return JNI_TRUE;
}
else
{
LOGE("RFCOMM async connect error (%d): %s (%d)",
nat->rfcomm_sock, strerror(errno), errno);
return JNI_FALSE;
}
}
} // fcntl(nat->rfcomm_sock ...)
#endif
return JNI_FALSE;
}
static jboolean interruptAsyncConnectNative(JNIEnv *env, jobject obj) {
//WRITEME
return JNI_TRUE;
}
static jint waitForAsyncConnectNative(JNIEnv *env, jobject obj,
jint timeout_ms) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
struct sockaddr_rc addr;
native_data_t *nat = get_native_data(env, obj);
env->SetIntField(obj, field_mTimeoutRemainingMs, timeout_ms);
if (nat->rfcomm_sock < 0) {
LOGE("%s: socket(RFCOMM) error: socket not created", __FUNCTION__);
return -1;
}
if (nat->rfcomm_connected > 0) {
LOGI("%s: RFCOMM is already connected!", __FUNCTION__);
return 1;
}
/* Do an asynchronous select() */
int n;
fd_set rset, wset;
struct timeval to;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_SET(nat->rfcomm_sock, &rset);
FD_SET(nat->rfcomm_sock, &wset);
if (timeout_ms >= 0) {
to.tv_sec = timeout_ms / 1000;
to.tv_usec = 1000 * (timeout_ms % 1000);
}
n = select(nat->rfcomm_sock + 1,
&rset,
&wset,
NULL,
(timeout_ms < 0 ? NULL : &to));
if (timeout_ms > 0) {
jint remaining = to.tv_sec*1000 + to.tv_usec/1000;
LOGI("Remaining time %ldms", (long)remaining);
env->SetIntField(obj, field_mTimeoutRemainingMs,
remaining);
}
if (n <= 0) {
if (n < 0) {
LOGE("select() on RFCOMM socket: %s (%d)",
strerror(errno),
errno);
return -1;
}
return 0;
}
/* n must be equal to 1 and either rset or wset must have the
file descriptor set. */
LOGI("select() returned %d.", n);
if (FD_ISSET(nat->rfcomm_sock, &rset) ||
FD_ISSET(nat->rfcomm_sock, &wset)) {
/* A trial async read() will tell us if everything is OK. */
char ch;
errno = 0;
int nr = read(nat->rfcomm_sock, &ch, 1);
/* It should be that nr != 1 because we just opened a socket
and we haven't sent anything over it for the other side to
respond... but one can't be paranoid enough.
*/
if (nr >= 0 || errno != EAGAIN) {
LOGE("RFCOMM async connect() error: %s (%d), nr = %d\n",
strerror(errno),
errno,
nr);
/* Clear the rfcomm_connected flag to cause this function
to re-create the socket and re-attempt the connect()
the next time it is called.
*/
nat->rfcomm_connected = 0;
/* Restore the blocking properties of the socket. */
fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
return -1;
}
/* Restore the blocking properties of the socket. */
fcntl(nat->rfcomm_sock, F_SETFL, nat->rfcomm_sock_flags);
LOGI("Successful RFCOMM socket connect.");
nat->rfcomm_connected = 3; // input and output
return 1;
}
#endif
return -1;
}
static jboolean shutdownNative(JNIEnv *env, jobject obj,
jboolean shutdownInput) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
/* NOTE: If you change the bcode to modify nat, make sure you
add synchronize(this) to the method calling this native
method.
*/
native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock < 0) {
LOGE("socket(RFCOMM) error: socket not created");
return JNI_FALSE;
}
int rc = shutdown(nat->rfcomm_sock,
shutdownInput ? SHUT_RD : SHUT_WR);
if (!rc) {
nat->rfcomm_connected &=
shutdownInput ? ~1 : ~2;
return JNI_TRUE;
}
#endif
return JNI_FALSE;
}
static jint isConnectedNative(JNIEnv *env, jobject obj) {
LOGI(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
const native_data_t *nat = get_native_data(env, obj);
return nat->rfcomm_connected;
#endif
return 0;
}
//@@@@@@@@@ bind to device???
static jboolean bindNative(JNIEnv *env, jobject obj, jstring device,
jint port) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
/* NOTE: If you change the code to modify nat, make sure you
add synchronize(this) to the method calling this native
method.
*/
const native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock < 0) {
LOGE("socket(RFCOMM) error: socket not created");
return JNI_FALSE;
}
struct sockaddr_rc laddr;
int lm;
lm = 0;
/*
lm |= RFCOMM_LM_MASTER;
lm |= RFCOMM_LM_AUTH;
lm |= RFCOMM_LM_ENCRYPT;
lm |= RFCOMM_LM_SECURE;
*/
if (lm && setsockopt(nat->rfcomm_sock, SOL_RFCOMM, RFCOMM_LM, &lm, sizeof(lm)) < 0) {
LOGE("Can't set RFCOMM link mode");
return JNI_FALSE;
}
laddr.rc_family = AF_BLUETOOTH;
bacpy(&laddr.rc_bdaddr, BDADDR_ANY);
laddr.rc_channel = port;
if (bind(nat->rfcomm_sock, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
LOGE("Can't bind RFCOMM socket");
return JNI_FALSE;
}
env->SetIntField(obj, field_mPort, port);
return JNI_TRUE;
#endif
return JNI_FALSE;
}
static jboolean listenNative(JNIEnv *env, jobject obj, jint backlog) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
/* NOTE: If you change the code to modify nat, make sure you
add synchronize(this) to the method calling this native
method.
*/
const native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock < 0) {
LOGE("socket(RFCOMM) error: socket not created");
return JNI_FALSE;
}
return listen(nat->rfcomm_sock, backlog) < 0 ? JNI_FALSE : JNI_TRUE;
#else
return JNI_FALSE;
#endif
}
static int set_nb(int sk, bool nb) {
int flags = fcntl(sk, F_GETFL);
if (flags < 0) {
LOGE("Can't get socket flags with fcntl(): %s (%d)",
strerror(errno), errno);
close(sk);
return -1;
}
flags &= ~O_NONBLOCK;
if (nb) flags |= O_NONBLOCK;
int status = fcntl(sk, F_SETFL, flags);
if (status < 0) {
LOGE("Can't set socket to nonblocking mode with fcntl(): %s (%d)",
strerror(errno), errno);
close(sk);
return -1;
}
return 0;
}
// Note: the code should check at a higher level to see whether
// listen() has been called.
#ifdef HAVE_BLUETOOTH
static int do_accept(JNIEnv* env, jobject object, int sock,
jobject newsock,
jfieldID out_address,
bool must_succeed) {
if (must_succeed && set_nb(sock, true) < 0)
return -1;
struct sockaddr_rc raddr;
int alen = sizeof(raddr);
int nsk = accept(sock, (struct sockaddr *) &raddr, &alen);
if (nsk < 0) {
LOGE("Error on accept from socket fd %d: %s (%d).",
sock,
strerror(errno),
errno);
if (must_succeed) set_nb(sock, false);
return -1;
}
char addr[BTADDR_SIZE];
get_bdaddr_as_string(&raddr.rc_bdaddr, addr);
env->SetObjectField(newsock, out_address, env->NewStringUTF(addr));
LOGI("Successful accept() on AG socket %d: new socket %d, address %s, RFCOMM channel %d",
sock,
nsk,
addr,
raddr.rc_channel);
if (must_succeed) set_nb(sock, false);
return nsk;
}
#endif /*HAVE_BLUETOOTH*/
static jobject acceptNative(JNIEnv *env, jobject obj,
jobject newsock, jint timeoutMs) {
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_sock < 0) {
LOGE("socket(RFCOMM) error: socket not created");
return JNI_FALSE;
}
if (newsock == NULL) {
LOGE("%s: newsock = NULL\n", __FUNCTION__);
return JNI_FALSE;
}
int nsk = -1;
if (timeoutMs < 0) {
/* block until accept() succeeds */
nsk = do_accept(env, obj, nat->rfcomm_sock,
newsock, field_mAddress, false);
if (nsk < 0) {
return NULL;
}
}
else {
/* wait with a timeout */
struct pollfd fds;
fds.fd = nat->rfcomm_sock;
fds.events = POLLIN | POLLPRI | POLLOUT | POLLERR;
env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, 0);
int n = poll(&fds, 1, timeoutMs);
if (n <= 0) {
if (n < 0) {
LOGE("listening poll() on RFCOMM socket: %s (%d)",
strerror(errno),
errno);
env->SetIntField(obj, field_mAcceptTimeoutRemainingMs, timeoutMs);
}
else {
LOGI("listening poll() on RFCOMM socket timed out");
}
return NULL;
}
LOGI("listening poll() on RFCOMM socket returned %d", n);
if (fds.fd == nat->rfcomm_sock) {
if (fds.revents & (POLLIN | POLLPRI | POLLOUT)) {
LOGI("Accepting connection.\n");
nsk = do_accept(env, obj, nat->rfcomm_sock,
newsock, field_mAddress, true);
if (nsk < 0) {
return NULL;
}
}
}
}
LOGI("Connection accepted, new socket fd = %d.", nsk);
native_data_t *newnat = get_native_data(env, newsock);
newnat->rfcomm_sock = nsk;
newnat->rfcomm_connected = 3;
return jniCreateFileDescriptor(env, nsk);
#else
return NULL;
#endif
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
{"initializeNativeDataNative", "()V", (void *)initializeNativeDataNative},
{"cleanupNativeDataNative", "()V", (void *)cleanupNativeDataNative},
{"createNative", "()Ljava/io/FileDescriptor;", (void *)createNative},
{"destroyNative", "()V", (void *)destroyNative},
{"connectNative", "(Ljava/lang/String;I)Z", (void *)connectNative},
{"connectAsyncNative", "(Ljava/lang/String;I)Z", (void *)connectAsyncNative},
{"interruptAsyncConnectNative", "()Z", (void *)interruptAsyncConnectNative},
{"waitForAsyncConnectNative", "(I)I", (void *)waitForAsyncConnectNative},
{"shutdownNative", "(Z)Z", (void *)shutdownNative},
{"isConnectedNative", "()I", (void *)isConnectedNative},
{"bindNative", "(Ljava/lang/String;I)Z", (void*)bindNative},
{"listenNative", "(I)Z", (void*)listenNative},
{"acceptNative", "(Landroid/bluetooth/RfcommSocket;I)Ljava/io/FileDescriptor;", (void*)acceptNative},
};
int register_android_bluetooth_RfcommSocket(JNIEnv *env) {
return AndroidRuntime::registerNativeMethods(env,
"android/bluetooth/RfcommSocket", sMethods, NELEM(sMethods));
}
} /* namespace android */

View File

@@ -390,17 +390,18 @@ jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply) {
return byteArray;
}
void get_bdaddr(const char *str, bdaddr_t *ba) {
int get_bdaddr(const char *str, bdaddr_t *ba) {
char *d = ((char *)ba) + 5, *endp;
int i;
for(i = 0; i < 6; i++) {
*d-- = strtol(str, &endp, 16);
if (*endp != ':' && i != 5) {
memset(ba, 0, sizeof(bdaddr_t));
return;
return -1;
}
str = endp + 1;
}
return 0;
}
void get_bdaddr_as_string(const bdaddr_t *ba, char *str) {

View File

@@ -144,7 +144,7 @@ jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply);
jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply);
jbyteArray dbus_returns_array_of_bytes(JNIEnv *env, DBusMessage *reply);
void get_bdaddr(const char *str, bdaddr_t *ba);
int get_bdaddr(const char *str, bdaddr_t *ba);
void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
bool debug_no_encrypt();