rpc/bt: change the way to get the available port on bluetooth.
Change the way to get the first available port on bluetooth.
Change-Id: I755f42665a53aeb2010dc0e43b89fe45231a080f
diff --git a/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java b/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
index 269c286..0bbb048 100644
--- a/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
+++ b/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
@@ -11,7 +11,6 @@
import android.util.Log;
import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
import org.joda.time.Duration;
@@ -19,7 +18,6 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
@@ -39,109 +37,120 @@
*/
class Bluetooth {
private static final String TAG = "Bluetooth";
- private static final List<Integer> BLUETOOTH_PORTS = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,
- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
static Listener listen(VContext ctx, String btAddr) throws VException {
String macAddr = getMACAddress(ctx, btAddr);
int port = getPortNumber(btAddr);
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- List<Integer> ports = null;
- if (port == 0) { // listen on the first available port.
- ports = new ArrayList(BLUETOOTH_PORTS);
- Collections.shuffle(ports);
- } else { // listen on a specific port only
- ports = ImmutableList.of(port);
+ BluetoothServerSocket socket = listenOnPort(port);
+ if (port == 0) {
+ // listen on the first available port. Get the port number.
+ port = getPortNumber(socket);
}
+ Log.d(TAG, String.format("listening on port %d", port));
Executor executor = VRuntimeImpl.getRuntimeExecutor(ctx);
if (executor == null) {
- throw new VException("NULL executor in context: did you derive this context from " +
- "the context returned by V.init()?");
+ throw new VException(
+ "NULL executor in context: did you derive this context from "
+ + "the context returned by V.init()?");
}
- VException lastError = null;
- for (int portNum : ports) {
- try {
- BluetoothServerSocket socket = listenOnPort(portNum);
- Log.d(TAG, String.format("listening on port %d", portNum));
- return new Listener(executor, socket, String.format("%s/%d", macAddr, portNum));
- } catch (VException e) {
- // OK, try the next one
- lastError = e;
- }
- }
- throw lastError;
+ return new Listener(executor, socket, String.format("%s/%d", macAddr, port));
}
- static void dial(VContext ctx, String btAddr, final Duration timeout,
- final Callback<Stream> callback) throws VException {
+ static void dial(
+ VContext ctx, String btAddr, final Duration timeout, final Callback<Stream> callback)
+ throws VException {
final String macAddr = getMACAddress(ctx, btAddr);
final int port = getPortNumber(btAddr);
final Executor executor = VRuntimeImpl.getRuntimeExecutor(ctx);
if (executor == null) {
- throw new VException("NULL executor in context: did you derive this context from " +
- "the context returned by V.init()?");
+ throw new VException(
+ "NULL executor in context: did you derive this context from "
+ + "the context returned by V.init()?");
}
- executor.execute(new Runnable() {
- @Override
- public void run() {
- final BluetoothDevice device =
- BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddr);
- try {
- // Create a socket to the remote device.
- // NOTE(spetrovic): Android's public methods currently only allow connection to
- // a UUID, which goes through SDP. Since we already have a remote port number,
- // we connect to it directly, invoking a hidden method using reflection.
- Method m = device.getClass().getMethod("createInsecureRfcommSocket",
- new Class[]{int.class});
- final BluetoothSocket socket = (BluetoothSocket) m.invoke(device, port);
- // Connect.
- Timer timer = null;
- if (timeout.getMillis() != 0) {
- timer = new Timer();
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- try {
- socket.close();
- } catch (IOException e) {
- System.err.println("Couldn't close BluetoothSocket.");
+ executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ final BluetoothDevice device =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddr);
+ try {
+ // Create a socket to the remote device.
+ // NOTE(spetrovic): Android's public methods currently only allow connection to
+ // a UUID, which goes through SDP. Since we already have a remote port number,
+ // we connect to it directly, invoking a hidden method using reflection.
+ Method m =
+ device.getClass()
+ .getMethod(
+ "createInsecureRfcommSocket",
+ new Class[] {int.class});
+ final BluetoothSocket socket = (BluetoothSocket) m.invoke(device, port);
+ // Connect.
+ Timer timer = null;
+ if (timeout.getMillis() != 0) {
+ timer = new Timer();
+ timer.schedule(
+ new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ System.err.println(
+ "Couldn't close BluetoothSocket.");
+ }
+ }
+ },
+ timeout.getMillis());
+ }
+ try {
+ socket.connect();
+ } catch (IOException e) {
+ callback.onFailure(
+ new VException("Couldn't connect: " + e.getMessage()));
+ } finally {
+ if (timer != null) {
+ timer.cancel();
}
}
- }, timeout.getMillis());
- }
- try {
- socket.connect();
- } catch (IOException e) {
- callback.onFailure(new VException("Couldn't connect: " + e.getMessage()));
- } finally {
- if (timer != null) {
- timer.cancel();
+ // There is no way currently to retrieve the local port number for the
+ // connection, but that's probably OK.
+ String localAddr = String.format("%s/%d", localMACAddress(ctx), 0);
+ String remoteAddr = String.format("%s/%d", macAddr, port);
+ callback.onSuccess(new Stream(executor, socket, localAddr, remoteAddr));
+ } catch (Exception e) {
+ callback.onFailure(
+ new VException(
+ "Couldn't invoke createInsecureRfcommSocket: "
+ + e.getMessage()));
}
}
- // There is no way currently to retrieve the local port number for the
- // connection, but that's probably OK.
- String localAddr = String.format("%s/%d", localMACAddress(ctx), 0);
- String remoteAddr = String.format("%s/%d", macAddr, port);
- callback.onSuccess(new Stream(executor, socket, localAddr, remoteAddr));
- } catch (Exception e) {
- callback.onFailure(new VException("Couldn't invoke createInsecureRfcommSocket: "
- + e.getMessage()));
- }
-
- }
- });
+ });
}
private static BluetoothServerSocket listenOnPort(int port) throws VException {
+ if (port == 0) {
+ // Use SOCKET_CHANNEL_AUTO_STATIC (-2) to auto assign a channel number.
+ port = -2;
+ }
// Use reflection to reach the hidden "listenUsingInsecureRfcommOn(port)" method.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
try {
- Method m = adapter.getClass().
- getMethod("listenUsingInsecureRfcommOn", new Class[]{int.class});
+ Method m =
+ adapter.getClass()
+ .getMethod("listenUsingInsecureRfcommOn", new Class[] {int.class});
return (BluetoothServerSocket) m.invoke(adapter, port);
} catch (Exception e) {
- throw new VException("Error invoking listenUsingInsecureRfcommOn: "
- + e.getMessage());
+ throw new VException("Error invoking listenUsingInsecureRfcommOn: " + e.getMessage());
+ }
+ }
+
+ private static int getPortNumber(BluetoothServerSocket serverSocket) throws VException {
+ // Use reflection to reach the hidden "getChannel()" method.
+ try {
+ Method m = serverSocket.getClass().getMethod("getChannel", new Class[0]);
+ return (int) m.invoke(serverSocket);
+ } catch (Exception e) {
+ throw new VException("Error invoking getChannel: " + e.getMessage());
}
}
@@ -150,16 +159,18 @@
// This is a remaining working hack that gets the local bluetooth address,
// just to get things working.
return android.provider.Settings.Secure.getString(
- V.getAndroidContext(ctx).getContentResolver(), "bluetooth_address");
+ V.getAndroidContext(ctx).getContentResolver(), "bluetooth_address");
}
private static String getMACAddress(VContext ctx, String btAddr) throws VException {
List<String> parts = Splitter.on("/").omitEmptyStrings().splitToList(btAddr);
switch (parts.size()) {
case 0:
- throw new VException(String.format(
- "Couldn't split bluetooth address \"%s\" using \"/\" separator: " +
- "got zero parts!", btAddr));
+ throw new VException(
+ String.format(
+ "Couldn't split bluetooth address \"%s\" using \"/\" separator: "
+ + "got zero parts!",
+ btAddr));
case 1:
return localMACAddress(ctx);
case 2:
@@ -169,8 +180,10 @@
}
return address;
default:
- throw new VException(String.format(
- "Couldn't parse bluetooth address \"%s\": too many \"/\".", btAddr));
+ throw new VException(
+ String.format(
+ "Couldn't parse bluetooth address \"%s\": too many \"/\".",
+ btAddr));
}
}
@@ -178,26 +191,33 @@
List<String> parts = Splitter.on("/").splitToList(btAddr);
switch (parts.size()) {
case 0:
- throw new VException(String.format(
- "Couldn't split bluetooth address \"%s\" using \"/\" separator: " +
- "got zero parts!", btAddr));
+ throw new VException(
+ String.format(
+ "Couldn't split bluetooth address \"%s\" using \"/\" separator: "
+ + "got zero parts!",
+ btAddr));
case 1:
case 2:
try {
int port = Integer.parseInt((parts.get(parts.size() - 1)));
if (port < 0 || port > 32) {
- throw new VException(String.format("Illegal port number %q in bluetooth " +
- "address \"%s\".", port, btAddr));
+ throw new VException(
+ String.format(
+ "Illegal port number %q in bluetooth " + "address \"%s\".",
+ port, btAddr));
}
return port;
} catch (NumberFormatException e) {
- throw new VException(String.format(
- "Couldn't parse port number in bluetooth address \"%s\": %s",
- btAddr, e.getMessage()));
+ throw new VException(
+ String.format(
+ "Couldn't parse port number in bluetooth address \"%s\": %s",
+ btAddr, e.getMessage()));
}
default:
- throw new VException(String.format(
- "Couldn't parse bluetooth address \"%s\": too many \"/\".", btAddr));
+ throw new VException(
+ String.format(
+ "Couldn't parse bluetooth address \"%s\": too many \"/\".",
+ btAddr));
}
}
@@ -213,22 +233,24 @@
}
void accept(final Callback<Stream> callback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- BluetoothSocket socket = serverSocket.accept();
- // There is no way currently to retrieve the remote end's channel number,
- // but that's probably OK.
- String remoteAddress =
- String.format("%s/%d", socket.getRemoteDevice().getAddress(), 0);
- callback.onSuccess(new Stream(
- executor, socket, localAddress, remoteAddress));
- } catch (IOException e) {
- callback.onFailure(new VException(e.getMessage()));
- }
- }
- });
+ executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ BluetoothSocket socket = serverSocket.accept();
+ // There is no way currently to retrieve the remote end's channel number,
+ // but that's probably OK.
+ String remoteAddress =
+ String.format(
+ "%s/%d", socket.getRemoteDevice().getAddress(), 0);
+ callback.onSuccess(
+ new Stream(executor, socket, localAddress, remoteAddress));
+ } catch (IOException e) {
+ callback.onFailure(new VException(e.getMessage()));
+ }
+ }
+ });
}
void close() throws IOException {
@@ -246,8 +268,11 @@
private final String localAddress;
private final String remoteAddress;
- Stream(Executor executor, BluetoothSocket socket, String localAddress,
- String remoteAddress) {
+ Stream(
+ Executor executor,
+ BluetoothSocket socket,
+ String localAddress,
+ String remoteAddress) {
this.executor = executor;
this.socket = socket;
this.localAddress = localAddress;
@@ -255,32 +280,35 @@
}
void read(final int n, final Callback<byte[]> callback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- byte[] buf = new byte[n];
- int num = socket.getInputStream().read(buf);
- callback.onSuccess(num == buf.length ? buf : Arrays.copyOf(buf, num));
- } catch (IOException e) {
- callback.onFailure(new VException(e.getMessage()));
- }
- }
- });
+ executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ byte[] buf = new byte[n];
+ int num = socket.getInputStream().read(buf);
+ callback.onSuccess(
+ num == buf.length ? buf : Arrays.copyOf(buf, num));
+ } catch (IOException e) {
+ callback.onFailure(new VException(e.getMessage()));
+ }
+ }
+ });
}
void write(final byte[] data, final Callback<Void> callback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- socket.getOutputStream().write(data);
- callback.onSuccess(null);
- } catch (IOException e) {
- callback.onFailure(new VException(e.getMessage()));
- }
- }
- });
+ executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ socket.getOutputStream().write(data);
+ callback.onSuccess(null);
+ } catch (IOException e) {
+ callback.onFailure(new VException(e.getMessage()));
+ }
+ }
+ });
}
void close() throws IOException {