Android 模拟蓝牙打印机

1: 思路

百度百科的介绍

所谓蓝牙打印机,就是指在主机端用一单片机来仿真打印机进行工作,截取从主机并口传出的数据及控制信号,并通过蓝牙无线连接传送到打印机端。在打印机侧的单片机则根据所收到的蓝牙数据来仿真主机对打印机进行控制操作,从而实现打印机与主机的蓝牙无线连接.

具体逻辑

由于蓝牙的协议栈比较复杂, 以及蓝牙的状态机相关的代码也比较多, 所以实现的方式可以通过framework代码注入一个假的蓝牙打印机设备, 然后用户可以连接这个打印机。
蓝牙打印机是通过Socket连接的, 所以可以在打开蓝牙的时候, 开启一个ServerSocket, 连接蓝牙打印机的时候, 真实就是连接这个ServerSocket。

注意事项

碰到一个巨坑的BUG, 口碑外卖, 蓝牙打印连接成功, 但是UI没有更新。
最后通过反编译的方式, 发现口碑的UI更新是通过监听蓝牙连接变化的广播实现的。
最后在虚拟蓝牙连接和断开的时候加上相应的广播就可以了。

    public static final String ACTION_ACL_CONNECTED =
            "android.bluetooth.device.action.ACL_CONNECTED";

    public static final String ACTION_ACL_DISCONNECT_REQUESTED =
            "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";

2: 实现

1: framework修改

BluetoothSocket.java 根据虚拟的蓝牙地址建立一个本地Socket。

public void connect() throws IOException {
  ...
  if(BluetoothDevice.BLUETOOTH_ADDRESS.equals(mAddress)){
                    Log.d(TAG, "Address: " + mAddress);
                    mSocket = new LocalSocket();
                    mSocket.connect(new LocalSocketAddress(BluetoothDevice.BLUETOOTH_NAME));
                    mSocketIS = mSocket.getInputStream();
                    mSocketOS = mSocket.getOutputStream();
                    return;
                }else{
                    mSocket = new LocalSocket(fd);
                }
  ...
}

2: Bluetooth修改

AdapterProperties.java 注入假的蓝牙设备

static void deviceFoundCallback(Context context) {
        BluetoothDevice device = BluetoothDevice.createSimulateDevice(BluetoothDevice.BLUETOOTH_ADDRESS);
        Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_CLASS, new BluetoothClass(BluetoothClass.Device.Major.IMAGING));
        intent.putExtra(BluetoothDevice.EXTRA_RSSI, 0);
        intent.putExtra(BluetoothDevice.EXTRA_NAME, "Simulate Imaging");
        context.sendBroadcast(intent, BLUETOOTH_PERM);
    }

实现一个ServerSocket的服务端监听蓝牙打印请求, AdapterService.java 开启和关闭这个ServerSocket.

3: 打印客户端

普通的打印服务

    public boolean connect() {
        Log.e("Gank", "1 connect()");
        System.out.println("2 connect()");
        if (!this.isConnection) {
            try {
                bluetoothSocket = this.device
                        .createRfcommSocketToServiceRecord(uuid);
                bluetoothSocket.connect();
                outputStream = bluetoothSocket.getOutputStream();
                this.isConnection = true;
                if (this.bluetoothAdapter.isDiscovering()) {
                    System.out.println("关闭适配器!");
                    this.bluetoothAdapter.isDiscovering();
                }
            } catch (Exception e) {
                Toast.makeText(this.context, "连接失败!", Toast.LENGTH_SHORT).show();
                Log.e("Gank", "", e);
                System.out.println("1" + e);
                return false;
            }
            Toast.makeText(this.context, this.device.getName() + "连接成功!",
                    Toast.LENGTH_SHORT).show();
            return true;
        } else {
            return true;
        }
    }
    /**
     * 发送数据
     */
    public void send(String sendData) {
        if (this.isConnection) {
            System.out.println("开始打印!!");
            try {
                byte[] data = sendData.getBytes();
                outputStream.write(data, 0, data.length);
                outputStream.flush();
            } catch (IOException e) {
                Toast.makeText(this.context, "发送失败!", Toast.LENGTH_SHORT)
                        .show();
            }
        } else {
            Toast.makeText(this.context, "设备未连接,请重新连接!", Toast.LENGTH_SHORT)
                    .show();

        }
    }

推荐阅读更多精彩内容