From c790096ad32d9f34263f31c502404e31e3c175db Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Tue, 26 May 2009 19:13:43 -0700 Subject: [PATCH] New BluetoothSocket API. Modeled on blocking java.net.Socket and java.net.ServerSocket library. Public interface is: public final class BluetoothSocket implements Closeable { public static BluetoothSocket createRfcommSocket(String address, int port) throws IOException; public static BluetoothSocket createInsecureRfcommSocket(String address, int port) throws IOException; public void connect() throws IOException; public void close() throws IOException; public String getAddress(); public InputStream getInputStream() throws IOException; public OutputStream getOutputStream() throws IOException; } public final class BluetoothServerSocket implements Closeable { public static BluetoothServerSocket listenUsingRfcommOn(int port) throws IOException; public static BluetoothServerSocket listenUsingUnsecureRfcommOn(int port) throws IOException; public BluetoothSocket accept() throws IOException; public BluetoothSocket accept(int timeout) throws IOException; public void close() throws IOException; } --- .../android/bluetooth/BluetoothInputStream.java | 62 ++ .../android/bluetooth/BluetoothOutputStream.java | 57 ++ .../android/bluetooth/BluetoothServerSocket.java | 124 ++++ core/java/android/bluetooth/BluetoothSocket.java | 176 ++++++ core/java/android/bluetooth/RfcommSocket.java | 674 --------------------- core/jni/Android.mk | 2 +- core/jni/AndroidRuntime.cpp | 4 +- core/jni/android_bluetooth_BluetoothSocket.cpp | 324 ++++++++++ core/jni/android_bluetooth_RfcommSocket.cpp | 621 ------------------- core/jni/android_bluetooth_common.cpp | 5 +- core/jni/android_bluetooth_common.h | 2 +- 11 files changed, 750 insertions(+), 1301 deletions(-) create mode 100644 core/java/android/bluetooth/BluetoothInputStream.java create mode 100644 core/java/android/bluetooth/BluetoothOutputStream.java create mode 100644 core/java/android/bluetooth/BluetoothServerSocket.java create mode 100644 core/java/android/bluetooth/BluetoothSocket.java delete mode 100644 core/java/android/bluetooth/RfcommSocket.java create mode 100644 core/jni/android_bluetooth_BluetoothSocket.cpp delete mode 100644 core/jni/android_bluetooth_RfcommSocket.cpp diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java new file mode 100644 index 000000000000..ceae70c586d3 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothInputStream.java @@ -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(); + } +} diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java new file mode 100644 index 000000000000..32e6d17c82a8 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothOutputStream.java @@ -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); + } +} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java new file mode 100644 index 000000000000..ca467011c91e --- /dev/null +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -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(); + } +} diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java new file mode 100644 index 000000000000..fd8885ece993 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -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(); +} diff --git a/core/java/android/bluetooth/RfcommSocket.java b/core/java/android/bluetooth/RfcommSocket.java deleted file mode 100644 index a33263f52612..000000000000 --- a/core/java/android/bluetooth/RfcommSocket.java +++ /dev/null @@ -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; - } -} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 9ad93eb05bbf..65f05913a568 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -102,7 +102,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 \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index c3104b8d66b6..3ad54933ca1e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -140,7 +140,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); @@ -1098,7 +1098,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), diff --git a/core/jni/android_bluetooth_BluetoothSocket.cpp b/core/jni/android_bluetooth_BluetoothSocket.cpp new file mode 100644 index 000000000000..08f9cb600b01 --- /dev/null +++ b/core/jni/android_bluetooth_BluetoothSocket.cpp @@ -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 +#include +#include +#include +#include + +#ifdef HAVE_BLUETOOTH +#include +#include +#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, "", "(IZZLjava/lang/String;I)V"); + return AndroidRuntime::registerNativeMethods(env, + "android/bluetooth/BluetoothSocket", sMethods, NELEM(sMethods)); +} + +} /* namespace android */ + diff --git a/core/jni/android_bluetooth_RfcommSocket.cpp b/core/jni/android_bluetooth_RfcommSocket.cpp deleted file mode 100644 index 3ed35d9017ec..000000000000 --- a/core/jni/android_bluetooth_RfcommSocket.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_BLUETOOTH -#include -#include -#include -#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 */ diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp index c81af1cea917..2bab2084d74d 100644 --- a/core/jni/android_bluetooth_common.cpp +++ b/core/jni/android_bluetooth_common.cpp @@ -384,17 +384,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) { diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h index c30ba22a3318..f0fbbb56da49 100644 --- a/core/jni/android_bluetooth_common.h +++ b/core/jni/android_bluetooth_common.h @@ -135,7 +135,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(); -- 2.11.0