一、引言
首先我们思考一个问题,普通的APP通过连接指定的蓝牙就可以实现设备间的传输,
那么如果要实现常驻蓝牙链接传输数据需要如何实现?其实思路很简单,就是模仿系统的
蓝牙实现,在android原生蓝牙上嫁接一个虚拟蓝牙,APP就可以与系统蓝牙长链接,本文
主要分为两部分解释实现,分别为APP端与系统端。
二、系统端
2.1、涉及的类
frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
frameworks/base/core/java/android/bluetooth/BluetoothSocket.java
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterProperties.java
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
packages/apps/Settings/src/com/android/settings/bluetooth/RemoteDeviceNameDialogFragment.java
packages/apps/Settings/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
2.1.1、framework层修改
1、首先在BluetoothDevice类中添加创建蓝牙设备的方法
/** @hide */
public static BluetoothDevice createVirtualBluetoothDevice(String address) {
return new BluetoothDevice(address);
}
2、然后在BluetoothSocket(连接设备的API类)类中的connect方法中根据地址创建一个虚拟蓝
牙,目前演示的地址命名为:00:11:22:33:44:55,蓝牙名称为:BluetoothPrinter。
/**
* Attempt to connect to a remote device.
* <p>This method will block until a connection is made or the connection
* fails. If this method returns without an exception then this socket
* is now connected.
* <p>Creating new connections to
* remote Bluetooth devices should not be attempted while device discovery
* is in progress. Device discovery is a heavyweight procedure on the
* Bluetooth adapter and will significantly slow a device connection.
* Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
* discovery. Discovery is not managed by the Activity,
* but is run as a system service, so an application should always call
* {@link BluetoothAdapter#cancelDiscovery()} even if it
* did not directly request a discovery, just to be sure.
* <p>{@link #close} can be used to abort this call from another thread.
*
* @throws IOException on error, for example connection failure
*/
public void connect() throws IOException {
if (mDevice == null) throw new IOException("Connect is called on null device");
Log.d("dzm","BluetoothSocket connect mDevice.getAddress() = " + mDevice.getAddress());
try {
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
IBluetooth bluetoothProxy =
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
synchronized (this) {
if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
if (mPfd == null) throw new IOException("bt socket connect failed");
//add by cgt start
String VirtualBluetoothAddress = "00:11:22:33:44:55";
String VirtualBluetoothName = "BluetoothPrinter";
if( mDevice.getAddress().equals(VirtualBluetoothAddress)){
Log.d("dzm","BluetoothSocket connect() -new LocalSocket if ");
mSocket = new LocalSocket();
mSocket.connect(new LocalSocketAddress(VirtualBluetoothName));
mSocketIS = mSocket.getInputStream();
mSocketOS = mSocket.getOutputStream();
Log.d("dzm","BluetoothSocket connect mSocketIS = " + mSocketIS);
Log.d("dzm","BluetoothSocket connect mSocketOS = " + mSocketOS);
mSocketState = SocketState.CONNECTED;
return ;
}
//add by cgt end
FileDescriptor fd = mPfd.getFileDescriptor();
mSocket = LocalSocket.createConnectedLocalSocket(fd);
mSocketIS = mSocket.getInputStream();
mSocketOS = mSocket.getOutputStream();
}
int channel = readInt(mSocketIS);
if (channel <= 0) {
throw new IOException("bt socket connect failed");
}
mPort = channel;
waitSocketSignal(mSocketIS);
synchronized (this) {
if (mSocketState == SocketState.CLOSED) {
throw new IOException("bt socket closed");
}
mSocketState = SocketState.CONNECTED;
}
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
throw new IOException("unable to send RPC: " + e.getMessage());
}
}
3、sepolicy权限的修改,因普通APP无法直接操作添加的虚拟蓝牙设备,需要修改权限APP
才能连上蓝牙,平台间加权限的地方可能有差异,殊途同归,本文只展示RK平台的权限修改
device/rockchip/common/sepolicy/vendor/untrusted_app.te
device/rockchip/common/sepolicy/vendor/untrusted_app_25.te
device/rockchip/common/sepolicy/vendor/untrusted_app_27.te
system/sepolicy/prebuilts/api/30.0/private/untrusted_app_29.te
system/sepolicy/private/untrusted_app_29.te
以上的类中把添加入权限
allow untrusted_app platform_app:unix_stream_socket connectto;
2.1.2、原生Bluetooth修改
1、首先在RemoteDevices类中添加虚拟蓝牙到远程设备列表中
//add by cgt start
public static final String VirtualBluetoothAddress = "00:11:22:33:44:55";
public static final byte[] VirtualBluetoothAddressbyte = new byte[]{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
public void addVirtualBluetoothDeviceProperties(){
Log.d("cgt","RemoteDevices addVirtualBluetoothDeviceProperties + " +SystemProperties.get("persist.sys.btname", "BluetoothPrinter"));
DeviceProperties prop = new DeviceProperties();
BluetoothDevice virtualDevice = BluetoothDevice.createVirtualBluetoothDevice(VirtualBluetoothAddress);
prop.mDevice = virtualDevice;
prop.mName = SystemProperties.get("persist.sys.btname", "BluetoothPrinter"); //"BluetoothPrinter";
prop.mAddress = VirtualBluetoothAddressbyte;
prop.mBluetoothClass = 0x0600;
prop.mDeviceType = 2;
prop.mBondState = 10;
synchronized (mDevices) {
DeviceProperties device = mDevices.get(virtualDevice);
if(device == null){
mDevices.put(VirtualBluetoothAddress, prop);
}
}
}
//add by cgt end
2、RemoteDevices类的内部类DeviceProperties中提供设置虚拟蓝牙名称的方法
void setVirtualName(String name) {
synchronized (mObject) {
this.mName = name;
}
}
3、AdapterService类中的startDiscovery方法中添加虚拟蓝牙设备,并发送广播通知
boolean startDiscovery(String callingPackage, @Nullable String callingFeatureId) {
UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
debugLog("startDiscovery");
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
boolean isQApp = Utils.isQApp(this, callingPackage);
String permission = null;
if (Utils.checkCallerHasNetworkSettingsPermission(this)) {
permission = android.Manifest.permission.NETWORK_SETTINGS;
} else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
} else if (isQApp) {
if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingFeatureId,
callingUser)) {
return false;
}
permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
} else {
if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingFeatureId,
callingUser)) {
return false;
}
permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
}
synchronized (mDiscoveringPackages) {
mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
}
//add by cgt start
Log.d("cgt","AdapterService startDiscovery() +");
mRemoteDevices.addVirtualBluetoothDeviceProperties();
BluetoothDevice virtualDevice = BluetoothDevice.createVirtualBluetoothDevice(RemoteDevices.VirtualBluetoothAddress);
Log.d("cgt","AdapterService startDiscovery() -VirtualBluetoothAddress = " + RemoteDevices.VirtualBluetoothAddress);
Intent intentVirtual = new Intent(BluetoothDevice.ACTION_FOUND);
intentVirtual.putExtra(BluetoothDevice.EXTRA_DEVICE,virtualDevice);
intentVirtual.putExtra(BluetoothDevice.EXTRA_NAME, SystemProperties.get("persist.sys.btname", "BluetoothPrinter"));
intentVirtual.putExtra(BluetoothDevice.EXTRA_CLASS, new BluetoothClass(BluetoothClass.Device.Major.IMAGING));
this.sendBroadcast(intentVirtual);
Log.d("cgt","AdapterService startDiscovery() - sendBroadcast BluetoothDevice.ACTION_FOUND");
//add by cgt end
return startDiscoveryNative();
}
4、 AdapterService类中的createBond方法中创建一个蓝牙连接的纽带,把虚拟蓝牙连上
boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
add by cgt start
if(Utils.getAddressStringFromByte(deviceProp.getAddress()).equals(RemoteDevices.VirtualBluetoothAddress)){
deviceProp.setBondState(12);
return true;
}
add by cgt end
return false;
}
mRemoteDevices.setBondingInitiatedLocally(Utils.getByteAddress(device));
// Pairing is unreliable while scanning, so cancel discovery
// Note, remove this when native stack improves
cancelDiscoveryNative();
Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
msg.obj = device;
msg.arg1 = transport;
if (oobData != null) {
Bundle oobDataBundle = new Bundle();
oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
msg.setData(oobDataBundle);
}
mBondStateMachine.sendMessage(msg);
return true;
}
5、AdapterProperties蓝牙设备适配器中把虚拟蓝牙设备选项添加进入列表
/**
* @return the mBondedDevices
*/
BluetoothDevice[] getBondedDevices() {
Log.d("cgt","getBondedDevices + ");
BluetoothDevice[] bondedDeviceList = new BluetoothDevice[0];
try {
//add by cgt start
Log.d("cgt","AdapterProperties getBondedDevices() -VirtualBluetoothAddress = " + RemoteDevices.VirtualBluetoothAddress);
BluetoothDevice device = BluetoothDevice.createVirtualBluetoothDevice(RemoteDevices.VirtualBluetoothAddress);
if(mRemoteDevices != null){
DeviceProperties prop = mRemoteDevices.getDeviceProperties(device);
if(prop == null){
Log.d("cgt","AdapterProperties getBondedDevices() - prop = null");
mRemoteDevices.addVirtualBluetoothDeviceProperties();
prop = mRemoteDevices.getDeviceProperties(device);
}else{
if (prop.getAddress() != null){
Log.d("cgt","BondedDevices is VirtualBluetooth Address: " + Arrays.equals(prop.getAddress(),RemoteDevices.VirtualBluetoothAddressbyte));
if(Arrays.equals(prop.getAddress(),RemoteDevices.VirtualBluetoothAddressbyte)){
prop.setVirtualName(SystemProperties.get("persist.sys.btname", "BluetoothPrinter"));
}
}
}
Log.d("cgt","AdapterProperties getBondedDevices() - setBondState=12");
prop.setBondState(12);
mBondedDevices.add(device);
}
//add by cgt end
bondedDeviceList = mBondedDevices.toArray(bondedDeviceList);
} catch (ArrayStoreException ee) {
errorLog("Error retrieving bonded device array");
}
infoLog("getBondedDevices: length=" + bondedDeviceList.length);
return bondedDeviceList;
}
2.1.3、原生Settings修改
1、RemoteDeviceNameDialogFragment类中的setDeviceName方法中设置虚拟蓝牙名称
@Override
protected void setDeviceName(String deviceName) {
if (mDevice != null) {
Log.d("cgt","RemoteDeviceNameDialogFragment mDevice.getAddress() = " + mDevice.getAddress() + " , deviceName = " + deviceName + " , deviceName.length: " + deviceName.length());
if(mDevice.getAddress().equals("00:11:22:33:44:55")){
/*if(deviceName.length()>85){
Toast.makeText(getContext(),"exceed the maximum length,pls input again",Toast.LENGTH_LONG).show();
return;
}*/
try {
SystemProperties.set("persist.sys.btname", deviceName);
} catch (Exception e) {
e.printStackTrace();
Log.e("cgt","setDeviceName error : " + e.getMessage());
Toast.makeText(getContext(),"exceed the maximum length,pls input again",Toast.LENGTH_LONG).show();
return;
}
}
mDevice.setName(deviceName);
}
}
2、SavedBluetoothDeviceUpdater类中的update方法中把虚拟蓝牙的项添加到设置的蓝牙
列表中
@Override
public void update(CachedBluetoothDevice cachedDevice) {
Log.d(TAG, "cgt update cachedDevice getAddress : " + cachedDevice.getAddress() + " , name : " + cachedDevice.getName());
if (isFilterMatched(cachedDevice) || ("00:11:22:33:44:55".equals(cachedDevice.getAddress()))) {
// Add the preference if it is new one
addPreference(cachedDevice, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
} else {
removePreference(cachedDevice);
}
}
到此系统端的内容就添加完毕,在设置的蓝牙中就会存在一个我们创建的虚拟蓝牙项
BluetoothPrinter,APP在打开蓝牙后连上此虚拟蓝牙进行传输数据。
三、APP端
实际上APP端只需要做两步,一个是连上对应的虚拟蓝牙,获取到socket对象后进行数据的传
输就可以,事例如下
private BluetoothSocket mSocket;
private OutputStream mOutputStream = null;
public BluetoothSocket connectDevice() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice("00:11:22:33:44:55");
BluetoothSocket socket = null;
try {
socket = device.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
mOutputStream = socket.getOutputStream();
} catch (IOException e) {
try {
socket.close();
} catch (IOException closeException) {
return null;
}
return null;
}
return socket;
}
public void sendDataSocket(byte[] bs){
try {
mOutputStream.write(bs);
} catch (IOException e) {
e.printStackTrace();
}
}
四、总结
虚拟蓝牙的介绍到此基本完成了,实际上只有在项目中有需求,才会去思考如何实现,如果
没有实际运用的地方,即使在厉害的技术也不会被采纳,不管是系统还是APP,其实都离不
开创造性思维,广度和深度都是要经过大量项目和技术的积累才能达到,下一篇更新实现静
默安装的方法。