Android7/10/11 系统实现虚拟蓝牙与APP通讯

一、引言

        首先我们思考一个问题,普通的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,其实都离不

        开创造性思维,广度和深度都是要经过大量项目和技术的积累才能达到,下一篇更新实现静

        默安装的方法。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驻足观雨听风吟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值