2012-10-10 36 views
22

Estoy trabajando en un proyecto en el que necesito conectarme mediante bluetooth a una impresora. El fabricante de la impresora afirma que solo los teléfonos con Android que tengan SPP (Perfil de puerto serie) podrán conectarse con la impresora.¿Debo dejar el reflejo de bluetooth pirateado en el código de producción?

Esta es la forma en que abrí la conexión inicialmente:

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

Uso UUID es la única manera de abrir conexiones RFCOMM usando API pública de Android como JellyBean. Al haber trabajado antes con conexiones SPP en BlackBerry y JavaME, donde no se requieren UUID, esto me pareció un poco extraño. Los UUID se refieren al descubrimiento de servicios, es decir, que usan SDP para consultar sobre los servicios presentes en el dispositivo. Realmente no necesito iniciar un descubrimiento, ya que tengo mi impresora emparejada por adelantado, y sé que es compatible con SPP. Sin embargo, eso es exactamente lo que hacen el método BluetoothDevice.createRfcommSocketToServiceRecord y la versión insegura. Esta es la pila SPP, donde podemos ver cómo SDP es un protocolo diferente en la misma capa, y por lo tanto debería ser posible utilizar RFCOMM sin iniciar un descubrimiento primera:

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

empecé a probar mi aplicación en una algunos viejos dispositivos HTC sin problemas. Más tarde, al probar en teléfonos Samsung, varios dispositivos no pudieron abrir una conexión. Estos teléfonos supuestamente no son compatibles con el perfil SPP, de acuerdo con las especificaciones del fabricante y de terceros (EDITAR: las especificaciones de terceros enumeran SPP como compatibles, y las especificaciones del fabricante no son lo suficientemente precisas). Una IOException (Descubrimiento de servicios no) fue lanzado, y me siguió el enfoque se muestra en esta pregunta:

Service discovery failed exception using Bluetooth on Android

La solución propuesta no es utilizar un truco de reflexión, de la siguiente manera:

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

El truco funcionó para mí. Sorprendentemente, este método en la clase BluetoothDevice es público, pero se elimina de la API mediante la anotación @hide. Este es el código fuente como de 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); 
     } 

No puedo entender por qué un método público se retira de la API de esta manera. Pero dejando esto aparte, tanto este método y el apoyo oficial usando los UUID son envolventes delgadas que llaman al mismo BluetoothSocket constructor con parámetros diferentes:

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

cavar un poco más en las fuentes me di cuenta de que tanto abrir una conexión RFCOMM, pero el método UUID inicia un descubrimiento y el oculto no.

Con todo, los hacks de reflexión funcionan perfectamente en todos los dispositivos que he probado, desde OS 2.2 a 4.1. EDITAR: Los dispositivos "defectuosos" no son compatibles con SPP, es solo que su implementación personalizada de la pila BT está jugando con el proceso de descubrimiento; También hay otros errores, como el que muestra el diálogo de emparejamiento para dispositivos ya emparejados en ICS. Llamar a esta API oculta mediante la reflexión permite una solución alternativa a todos estos errores o comportamientos diferentes introducidos por los diferentes fabricantes.

¿Debo mantener el truco en el código de producción? ¿Hay alguna manera de lograr lo mismo con la API pública?

Gracias de antemano.

Respuesta

16

Magnífica pregunta. Básicamente puedes usar la reflexión todo lo que quieras. Incluso hice algo similar para calcular los tiempos de lanzamiento de la aplicación, obtuve un método a través de la reflexión y funciona como un hechizo de FroYo hasta Jelly Bean. La única advertencia que necesita para hacer ejercicio es esto,

  • Dado que no es una API pública, Google puede cambiar en cualquier momento sin previo aviso
  • Si cambia, las aplicaciones del sistema o HAL que utiliza será en consecuencia modificado sin que ninguna aplicación se vea afectada.

¿Dónde tendrá que tener cuidado?

Es probable que los argumentos de este método puedan modificarse en el futuro.

Por lo tanto, deberá verificarlo con cada nueva versión del sistema operativo para que su aplicación no se rompa. De lo contrario, no tiene que preocuparse por usar este truco. Muchas aplicaciones usan tales ataques cuando las cosas no están expuestas con la API.

+8

No solo puede "google (sic) cambiarlo en cualquier momento sin previo aviso", sino que los fabricantes de dispositivos pueden "cambiarlo en cualquier momento sin previo aviso". No hay garantía de que este método exista, o funcione, en cualquier dispositivo Android dado. – CommonsWare

Cuestiones relacionadas