Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I leave the bluetooth reflection hack in production code?

I'm working in a project where I need to connect via bluetooth to a printer. The printer manufacturer states that only android phones having SPP (Serial Port Profile) are going to be able to connect with the printer.

This is how I opened the connection initially:

        UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //SPP long UUID
        BluetoothSocket socket = device.createRfcommSocketToServiceRecord(uuid);

Using UUIDs is the only way to open RFCOMM connections using Android public API as of JellyBean. Having worked before with SPP connections in BlackBerry and JavaME, where UUIDs are not required, I found this a bit odd. UUIDs are about service discovery, that is, using SDP to query about the services present in the device. I don't really need to initiate a discovery, since I have my printer paired in advance, and I know it supports SPP. However that is exactly what the BluetoothDevice.createRfcommSocketToServiceRecord method and the insecure version do. This is the SPP stack, where we can see how SDP is a different protocol at the same layer, and thus it should be possible to use RFCOMM without initiating a discovery first:

        -----------------------------------
        |        My Application           |
        ----------------------------------- 
        |     Serial Port Emulation       |
        |          or other API           |
        -----------------------------------
        |      RFCOMM          |    SDP   |
        -----------------------------------
        |  LMP   |   L2PCAP               |
        -----------------------------------
        |           Baseband              |
        -----------------------------------

I started testing my app in a few old HTC devices without problems. Later, testing on Samsung phones, several devices were unable to open a connection. These phones allegedly do not support SPP profile, according to both manufacturer and 3rd party specs(EDIT: 3rd party specs list SPP as supported, and the manufacturer specs are not accurate enough). An IOException (Service Discovery failed) was thrown, and I followed the approach shown in this question:

Service discovery failed exception using Bluetooth on Android

The solution proposed there is to use a reflection hack, as follows:

        Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
        BluetoothSocket socket = socket = (BluetoothSocket) m.invoke(device, 1);

The hack worked for me. Amazingly, this method in BluetoothDevice class is public, but it is removed from the API by means of the @hide annotation. This is the source code as of JellyBean:

        /**
         * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
         * outgoing connection to this remote device on given channel.
         * <p>The remote device will be authenticated and communication on this
         * socket will be encrypted.
         * <p> Use this socket only if an authenticated socket link is possible.
         * Authentication refers to the authentication of the link key to
         * prevent man-in-the-middle type of attacks.
         * For example, for Bluetooth 2.1 devices, if any of the devices does not
         * have an input and output capability or just has the ability to
         * display a numeric key, a secure socket connection is not possible.
         * In such a case, use {#link createInsecureRfcommSocket}.
         * For more details, refer to the Security Model section 5.2 (vol 3) of
         * Bluetooth Core Specification version 2.1 + EDR.
         * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
         * connection.
         * <p>Valid RFCOMM channels are in range 1 to 30.
         * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
         *
         * @param channel RFCOMM channel to connect to
         * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
         * @throws IOException on error, for example Bluetooth not available, or
         *                     insufficient permissions
         * @hide
         */
        public BluetoothSocket createRfcommSocket(int channel) throws IOException {
            return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
                    null);
        }

I can't understand why a public method is removed from the API in this manner. But letting this appart, both this method and the officially supported one using UUIDs are thin envelopes calling the same BluetoothSocket constructor with different parameters:

        public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
            return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
                    new ParcelUuid(uuid));
        }

Digging a bit more in the sources I realized that both open a RFCOMM connection, but the UUID method initiates a discovery and the hidden one not.

All in all, the reflection hacks work flawlessly in every device I've tested it, from OS 2.2 to 4.1. EDIT: The "faulty" devices do support SPP, it is just that their custom implementation of BT stack is messing up with the discovery process; there are also other bugs like the the one showing pairing dialog for already paired devices in ICS. Calling this hidden API using reflection enables a workaround to all these bugs or different behaviour introduced by the different manufacturers.

Should I keep the hack in production code? Is there a way to achieve the same with the public API?

Thanks in advance.

like image 948
Mister Smith Avatar asked Oct 10 '12 12:10

Mister Smith


1 Answers

Superb question. Basically you can use reflection all you want. Even i did something similar inorder to calculate the application launch times, got a method via reflection and its working like a charm from FroYo till Jelly Bean. The only word of caution you need to exercise is this,

  • Since it is not a public API, google can change it anytime without warning
  • If it changes, the system apps or HAL which uses it will be accordingly modified without any applications getting affected.

Where will you need to be careful?

Chances are the arguments of this method may get modified in the future.

So you will need to check for this with each new OS release so that your application does not break. Otherwise you need not worry about using this hack. Many applications use such hacks when things arent exposed with the API.

like image 80
Royston Pinto Avatar answered Oct 21 '22 00:10

Royston Pinto