Merge change 2007
* changes: New BluetoothSocket API.
This commit is contained in:
62
core/java/android/bluetooth/BluetoothInputStream.java
Normal file
62
core/java/android/bluetooth/BluetoothInputStream.java
Normal 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();
|
||||
}
|
||||
}
|
||||
57
core/java/android/bluetooth/BluetoothOutputStream.java
Normal file
57
core/java/android/bluetooth/BluetoothOutputStream.java
Normal 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);
|
||||
}
|
||||
}
|
||||
124
core/java/android/bluetooth/BluetoothServerSocket.java
Normal file
124
core/java/android/bluetooth/BluetoothServerSocket.java
Normal 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();
|
||||
}
|
||||
}
|
||||
176
core/java/android/bluetooth/BluetoothSocket.java
Normal file
176
core/java/android/bluetooth/BluetoothSocket.java
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
@@ -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),
|
||||
|
||||
324
core/jni/android_bluetooth_BluetoothSocket.cpp
Normal file
324
core/jni/android_bluetooth_BluetoothSocket.cpp
Normal 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 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user