bluetoothTool.js 7.99 KB
/**
 * 经典蓝牙工具(仅 Android APP-PLUS)
 * 用于佳博 D320FAX 等一体机的 Virtual BT Printer 连接
 * 支持 RFCOMM 回退连接(部分设备需要)
 */
// #ifdef APP-PLUS
function getAdapter () {
  if (typeof plus === 'undefined' || !plus.android) return null
  const BluetoothAdapter = plus.android.importClass('android.bluetooth.BluetoothAdapter')
  return BluetoothAdapter.getDefaultAdapter()
}

function getActivity () {
  return typeof plus !== 'undefined' ? plus.android.runtimeMainActivity() : null
}

function showToast (msg) {
  try {
    const activity = getActivity()
    if (activity) {
      const Toast = plus.android.importClass('android.widget.Toast')
      Toast.makeText(activity, String(msg), Toast.LENGTH_SHORT).show()
    }
  } catch (_) {}
}

function getInvoke () {
  return typeof plus !== 'undefined' && plus.android ? plus.android.invoke : null
}
function getMyUuid () {
  if (typeof plus === 'undefined' || !plus.android) return null
  return plus.android.importClass('java.util.UUID').fromString('00001101-0000-1000-8000-00805F9B34FB')
}
let btSocket = null
let btInStream = null
let btOutStream = null
let btFindReceiver = null
// #endif

var blueToothTool = {
  state: { bluetoothEnable: false, readThreadState: false },
  shortToast (msg) {
    // #ifdef APP-PLUS
    showToast(msg)
    // #endif
  },
  getBluetoothStatus () {
    // #ifdef APP-PLUS
    const btAdapter = getAdapter()
    return btAdapter != null && btAdapter.isEnabled()
    // #endif
    return false
  },
  /** 获取已配对设备(含 Virtual BT Printer / D320FAX) */
  getPairedDevices () {
    // #ifdef APP-PLUS
    const pairedDevices = []
    try {
      const btAdapter = getAdapter()
      if (!btAdapter || !btAdapter.isEnabled()) {
        this.shortToast('Bluetooth is off')
        return pairedDevices
      }
      const bonded = btAdapter.getBondedDevices()
      if (!bonded) return pairedDevices
      const inv = getInvoke()
      if (!inv) return pairedDevices
      const it = inv(bonded, 'iterator')
      while (inv(it, 'hasNext')) {
        const device = inv(it, 'next')
        const deviceType = inv(device, 'getType')
        const deviceId = inv(device, 'getAddress')
        let deviceName = inv(device, 'getName')
        if (deviceName == null) deviceName = ''
        deviceName = String(deviceName).trim() || 'Unknown Device'
        let typeStr = 'unknown'
        if (deviceType === 1) typeStr = 'classic'
        else if (deviceType === 2) typeStr = 'ble'
        else if (deviceType === 3) typeStr = 'dual'
        pairedDevices.push({ name: deviceName, deviceId: String(deviceId), type: typeStr })
      }
    } catch (e) {
      console.error('getPairedDevices error:', e)
    }
    return pairedDevices
    // #endif
    return []
  },
  /** 经典蓝牙扫描(发现未配对设备,如 d320fax_295c)— 不过滤任何设备 */
  startClassicDiscovery (onDeviceFound, onDiscoveryFinished) {
    // #ifdef APP-PLUS
    const btAdapter = getAdapter()
    const activity = getActivity()
    if (!btAdapter || !btAdapter.isEnabled() || !activity) return
    if (btFindReceiver) {
      try { activity.unregisterReceiver(btFindReceiver) } catch (_) {}
      btFindReceiver = null
    }
    if (btAdapter.isDiscovering()) btAdapter.cancelDiscovery()
    const BluetoothAdapter = plus.android.importClass('android.bluetooth.BluetoothAdapter')
    const BluetoothDevice = plus.android.importClass('android.bluetooth.BluetoothDevice')
    const IntentFilter = plus.android.importClass('android.content.IntentFilter')
    const inv = getInvoke()
    btFindReceiver = plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
      onReceive (context, intent) {
        const action = intent.getAction()
        if (BluetoothDevice.ACTION_FOUND === action) {
          const device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
          if (!device) return
          const deviceType = inv(device, 'getType')
          const deviceId = String(inv(device, 'getAddress') || '')
          let deviceName = inv(device, 'getName')
          if (deviceName == null) deviceName = ''
          deviceName = String(deviceName).trim() || 'Unknown Device'
          let typeStr = 'unknown'
          if (deviceType === 1) typeStr = 'classic'
          else if (deviceType === 2) typeStr = 'ble'
          else if (deviceType === 3) typeStr = 'dual'
          if (onDeviceFound) onDeviceFound({ name: deviceName, deviceId, type: typeStr })
        }
        if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED === action) {
          try { activity.unregisterReceiver(btFindReceiver) } catch (_) {}
          btFindReceiver = null
          if (onDiscoveryFinished) onDiscoveryFinished()
        }
      },
    })
    const filter = new IntentFilter()
    filter.addAction(BluetoothDevice.ACTION_FOUND)
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
    activity.registerReceiver(btFindReceiver, filter)
    btAdapter.startDiscovery()
    // #endif
  },
  /** 停止经典蓝牙扫描 */
  cancelClassicDiscovery () {
    // #ifdef APP-PLUS
    const btAdapter = getAdapter()
    const activity = getActivity()
    if (btAdapter && btAdapter.isDiscovering()) btAdapter.cancelDiscovery()
    if (btFindReceiver && activity) {
      try { activity.unregisterReceiver(btFindReceiver) } catch (_) {}
      btFindReceiver = null
    }
    // #endif
  },
  /** 连接经典蓝牙设备(含 D320FAX Virtual BT Printer,支持 RFCOMM 回退) */
  connDevice (address, callback) {
    // #ifdef APP-PLUS
    if (btSocket != null) this.closeBtSocket()
    this.state.readThreadState = false
    const btAdapter = getAdapter()
    if (!btAdapter) {
      this.shortToast('Bluetooth not available')
      if (callback) callback(false)
      return false
    }
    const inv = getInvoke()
    const uuid = getMyUuid()
    if (!inv || !uuid) {
      this.shortToast('Bluetooth not ready')
      if (callback) callback(false)
      return false
    }
    try {
      const device = inv(btAdapter, 'getRemoteDevice', address)
      btSocket = inv(device, 'createRfcommSocketToServiceRecord', uuid)
    } catch (e) {
      console.warn('createRfcommSocketToServiceRecord failed, try fallback:', e)
      try {
        const device = inv(btAdapter, 'getRemoteDevice', address)
        const cls = inv(device, 'getClass')
        const intClass = plus.android.importClass('java.lang.Integer').TYPE
        const m = inv(cls, 'getMethod', 'createRfcommSocket', intClass)
        btSocket = inv(m, 'invoke', device, 1)
      } catch (e2) {
        console.error('RFCOMM fallback failed:', e2)
        this.shortToast('Connect failed')
        if (callback) callback(false)
        return false
      }
    }
    try {
      inv(btSocket, 'connect')
      btInStream = inv(btSocket, 'getInputStream')
      btOutStream = inv(btSocket, 'getOutputStream')
      this.state.readThreadState = true
      this.shortToast('Connected')
      if (callback) callback(true)
    } catch (e) {
      try { btSocket.close() } catch (_) {}
      btSocket = null
      this.shortToast('Connect failed')
      if (callback) callback(false)
      return false
    }
    return true
    // #endif
    if (callback) callback(false)
    return false
  },
  disConnDevice () {
    // #ifdef APP-PLUS
    this.closeBtSocket()
    this.state.readThreadState = false
    this.shortToast('Disconnected')
    // #endif
  },
  closeBtSocket () {
    // #ifdef APP-PLUS
    this.state.readThreadState = false
    if (btSocket) {
      try { btSocket.close() } catch (_) {}
      btSocket = null
      btInStream = null
      btOutStream = null
    }
    // #endif
  },
  sendByteData (byteData) {
    // #ifdef APP-PLUS
    if (!btOutStream) {
      this.shortToast('Not connected')
      return false
    }
    try {
      const CHUNK_SIZE = 4096
      for (let i = 0; i < byteData.length; i += CHUNK_SIZE) {
        const chunk = byteData.slice(i, i + CHUNK_SIZE)
        btOutStream.write(chunk)
      }
      return true
    } catch (e) {
      return false
    }
    // #endif
    return false
  }
}

export default blueToothTool