前言
本篇为蓝牙HID系列篇章之一,本篇以红米K30(MIUI13即Android 12)手机作为蓝牙HID设备,可以与电脑、手机、平板等其他蓝牙主机进行配对从而实现鼠标触控板的功能。
蓝牙HID系列篇章:
蓝牙HID——将android设备变成蓝牙键盘(BluetoothHidDevice)
蓝牙HID——android利用手机来解锁电脑/平板/iPhone
蓝牙HID——Android手机注册HID时出现 Could not bind to Bluetooth HID Device) Service with Intent * 的问题分析
HID开发
Android 9开放了 BluetoothHidDevice
等HID相关的API,通过与系统蓝牙HID服务通信注册成蓝牙HID设备。首先通过 BluetoothProfile.HID_DEVICE
的描述类型得到 BluetoothHidDevice
抽象实例:
private BluetoothAdapter mBtAdapter;private BluetoothHidDevice mHidDevice;private void callBluetooth) {Log.dTAG, "callBluetooth");mBtAdapter = BluetoothAdapter.getDefaultAdapter);mBtAdapter.getProfileProxymContext, new BluetoothProfile.ServiceListener) {@Overridepublic void onServiceConnectedint i, BluetoothProfile bluetoothProfile) {Log.dTAG, "onServiceConnected:" + i);if i == BluetoothProfile.HID_DEVICE) {if !bluetoothProfile instanceof BluetoothHidDevice)) {Log.eTAG, "Proxy received but it's not BluetoothHidDevice");return;}mHidDevice = BluetoothHidDevice) bluetoothProfile;registerBluetoothHid);}}@Overridepublic void onServiceDisconnectedint i) {Log.dTAG, "onServiceDisconnected:" + i);}}, BluetoothProfile.HID_DEVICE);}
再调用 BluetoothHidDevice.registerApp)
将 Android 设备注册成蓝牙HID设备:
private BluetoothDevice mHostDevice;private final BluetoothHidDeviceAppQosSettings qosSettings= new BluetoothHidDeviceAppQosSettingsBluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,800, 9, 0, 11250, BluetoothHidDeviceAppQosSettings.MAX);private final BluetoothHidDeviceAppSdpSettings mouseSdpSettings = new BluetoothHidDeviceAppSdpSettingsHidConfig.MOUSE_NAME, HidConfig.DESCRIPTION, HidConfig.PROVIDER,BluetoothHidDevice.SUBCLASS1_MOUSE, HidConfig.MOUSE_COMBO);private void registerBluetoothHid) {if mHidDevice == null) {Log.eTAG, "hid device is null");return;}mHidDevice.registerAppmouseSdpSettings, null, qosSettings, Executors.newCachedThreadPool), new BluetoothHidDevice.Callback) {@Overridepublic void onAppStatusChangedBluetoothDevice pluggedDevice, boolean registered) {Log.dTAG, "onAppStatusChanged:" + pluggedDevice != null ? pluggedDevice.getName) : "null") + " registered:" + registered);if registered) {Log.dTAG, "paired devices: " + mHidDevice.getConnectionStatepluggedDevice));if pluggedDevice != null && mHidDevice.getConnectionStatepluggedDevice) != BluetoothProfile.STATE_CONNECTED) {boolean result = mHidDevice.connectpluggedDevice);Log.dTAG, "hidDevice connect:" + result);}}if mBluetoothHidStateListener != null) {mBluetoothHidStateListener.onRegisterStateChangedregistered, pluggedDevice != null);}}@Overridepublic void onConnectionStateChangedBluetoothDevice device, int state) {Log.dTAG, "onConnectionStateChanged:" + device + " state:" + state);if state == BluetoothProfile.STATE_CONNECTED) {mHostDevice = device;}if state == BluetoothProfile.STATE_DISCONNECTED) {mHostDevice = null;}if mBluetoothHidStateListener != null) {mBluetoothHidStateListener.onConnectionStateChangedstate);}}});}
蓝牙鼠标Mouse的描述信息如下,主要 为 MOUSE_COMBO
的描述协议,正确的描述协议才能成功与其他设备通信。
public class HidConfig {public final static String MOUSE_NAME = "VV Mouse";public final static String DESCRIPTION = "VV for you";public final static String PROVIDER = "VV";public static final byte[] MOUSE_COMBO = {byte) 0x05, byte) 0x01, // USAGE_PAGE Generic Desktop)byte) 0x09, byte) 0x02, // USAGE Mouse)byte) 0xa1, byte) 0x01, // COLLECTION Application)byte) 0x85, byte) 0x04, // REPORT_ID 4)byte) 0x09, byte) 0x01, // USAGE Pointer)byte) 0xa1, byte) 0x00, // COLLECTION Physical)byte) 0x05, byte) 0x09, // USAGE_PAGE Button)byte) 0x19, byte) 0x01, // USAGE_MINIMUM Button 1)byte) 0x29, byte) 0x02, // USAGE_MAXIMUM Button 2)byte) 0x15, byte) 0x00, // LOGICAL_MINIMUM 0)byte) 0x25, byte) 0x01, // LOGICAL_MAXIMUM 1)byte) 0x95, byte) 0x03, // REPORT_COUNT 3)byte) 0x75, byte) 0x01, // REPORT_SIZE 1)byte) 0x81, byte) 0x02, // INPUT Data,Var,Abs)byte) 0x95, byte) 0x01, // REPORT_COUNT 1)byte) 0x75, byte) 0x05, // REPORT_SIZE 5)byte) 0x81, byte) 0x03, // INPUT Cnst,Var,Abs)byte) 0x05, byte) 0x01, // USAGE_PAGE Generic Desktop)byte) 0x09, byte) 0x30, // USAGE X)byte) 0x09, byte) 0x31, // USAGE Y)byte) 0x09, byte) 0x38, // USAGE Wheel)byte) 0x15, byte) 0x81, // LOGICAL_MINIMUM -127)byte) 0x25, byte) 0x7F, // LOGICAL_MAXIMUM 127)byte) 0x75, byte) 0x08, // REPORT_SIZE 8)byte) 0x95, byte) 0x03, // REPORT_COUNT 3)byte) 0x81, byte) 0x06, // INPUT Data,Var,Rel)//水平滚轮byte) 0x05, byte) 0x0c, // USAGE_PAGE Consumer Devices)byte) 0x0a, byte) 0x38, byte) 0x02, // USAGE AC Pan)byte) 0x15, byte) 0x81, // LOGICAL_MINIMUM -127)byte) 0x25, byte) 0x7f, // LOGICAL_MAXIMUM 127)byte) 0x75, byte) 0x08, // REPORT_SIZE 8)byte) 0x95, byte) 0x01, // REPORT_COUNT 1)byte) 0x81, byte) 0x06, // INPUT Data,Var,Rel)byte) 0xc0, // END_COLLECTIONbyte) 0xc0, // END_COLLECTION};
在注册完成后启动设备发现,让HID能被其他设备发现,下面ActivityResultLauncher.launchnew IntentBluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)
相当于调用 BluetoothAdapter.setScanMode)
的隐藏API
private ActivityResultLauncher<Intent> mActivityResultLauncher;@Overrideprotected void onCreateBundle savedInstanceState) {super.onCreatesavedInstanceState);setContentViewR.layout.activity_mouse);mActivityResultLauncher = registerForActivityResultnew ActivityResultContracts.StartActivityForResult), result -> {Log.dTAG, "onActivityResult:" + result.toString));});}@Overridepublic void onRegisterStateChangedboolean registered, boolean hasDevice) {if registered) {if !hasDevice) {// startActivityForResultnew IntentBluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 1);mActivityResultLauncher.launchnew IntentBluetoothAdapter.ACTION_REQUEST_DISCOVERABLE));}}}
ActivityResultLauncher
的相关方法也可用 startActivityForResultnew IntentBluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), REQUEST_CODE)
来替代,但 startActivityForResult)
是废弃的方法,不建议使用。
接下来与蓝牙主机(电脑、手机等)进行蓝牙配对,已配对过需要取消配对。配对完成即可实现对蓝牙主机的鼠标触摸控制。
手势识别
手势识别通过对触摸事件以及手势监听进行各种手势的判断(移动鼠标、左键单击、左键双击、右键双指单击、双指垂直/水平滚动)。
CustomMotionListener customMotionListener = new CustomMotionListenerthis, mBluetoothHidManager);
findViewByIdR.id.layout_touch).setOnTouchListenercustomMotionListener);
手势逻辑处理代码如下:
package com.example.bluetoothproject;import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;import org.apache.commons.lang3.concurrent.BasicThreadFactory;import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class CustomMotionListener implements View.OnTouchListener, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {private final GestureDetector mGestureDetector;private BluetoothHidManager mBluetoothHidManager;private int mPointCount;private long mDoubleFingerTime;private final ScheduledExecutorService mExecutorService;private float mPreX;private float mPreY;private boolean mLongPress;public CustomMotionListenerContext context, BluetoothHidManager bluetoothHidManager) {mBluetoothHidManager = bluetoothHidManager;mGestureDetector = new GestureDetectorcontext, this);mGestureDetector.setOnDoubleTapListenerthis);mExecutorService = new ScheduledThreadPoolExecutor1,new BasicThreadFactory.Builder).namingPattern"mouse-schedule-pool-%d").daemontrue).build));}@Overridepublic boolean onSingleTapConfirmedMotionEvent e) {return false;}@Overridepublic boolean onDoubleTapMotionEvent e) {return false;}@Overridepublic boolean onDoubleTapEventMotionEvent e) {//左键单指双击(选中文本的效果)if e.getAction) == MotionEvent.ACTION_DOWN) {mBluetoothHidManager.sendLeftClicktrue);} else if e.getAction) == MotionEvent.ACTION_UP) {mBluetoothHidManager.sendLeftClickfalse);}return true;}@Overridepublic boolean onDownMotionEvent e) {return false;}@Overridepublic void onShowPressMotionEvent e) {}@Overridepublic boolean onSingleTapUpMotionEvent e) {//左键单击mBluetoothHidManager.sendLeftClicktrue);mBluetoothHidManager.sendLeftClickfalse);return true;}@Overridepublic boolean onScrollMotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//双指滚动,x为水平滚动,y为垂直滚动,消抖处理if mPointCount == 2) {if Math.absdistanceX) > Math.absdistanceY)) {distanceX = distanceX > 0 ? 1 : distanceX < 0 ? -1 : 0;distanceY = 0;} else {distanceY = distanceY > 0 ? -1 : distanceY < 0 ? 1 : 0;distanceX = 0;}mBluetoothHidManager.sendWheelbyte) distanceX), byte) distanceY));}return false;}@Overridepublic void onLongPressMotionEvent e) {//单键长按效果mBluetoothHidManager.sendLeftClicktrue);mLongPress = true;}@Overridepublic boolean onFlingMotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {return false;}@Overridepublic boolean onTouchView v, MotionEvent event) {float x = event.getX);float y = event.getY);if mGestureDetector.onTouchEventevent)) {return true;}mPointCount = event.getPointerCount);switch event.getActionMasked)) {case MotionEvent.ACTION_POINTER_DOWN://双指单击代表右键记录时间if event.getPointerCount) == 2) {mDoubleFingerTime = System.currentTimeMillis);}break;case MotionEvent.ACTION_MOVE://单指代表移动鼠标if event.getPointerCount) == 1) {float dx = x - mPreX;if dx > 127) dx = 127;if dx < -128) dx = -128;float dy = y - mPreY;if dy > 127) dy = 127;if dy < -128) dy = -128;mBluetoothHidManager.senMousebyte) dx, byte) dy);} else {mBluetoothHidManager.senMousebyte) 0, byte) 0);}break;case MotionEvent.ACTION_UP:if mLongPress) {mBluetoothHidManager.sendLeftClickfalse);mLongPress = false;}break;case MotionEvent.ACTION_POINTER_UP://双指按下代表右键if event.getPointerCount) == 2 && System.currentTimeMillis) - mDoubleFingerTime < ViewConfiguration.getDoubleTapTimeout)) {mBluetoothHidManager.sendRightClicktrue);//延时释放避免无效mExecutorService.scheduleWithFixedDelaynew Runnable) {@Overridepublic void run) {mBluetoothHidManager.sendRightClickfalse);}}, 0, 50, TimeUnit.MILLISECONDS); }break;default:break;}mPreX = x;mPreY = y;return true;}
}
向蓝牙主机发送的鼠标触摸按键的报告如下:
private boolean mLeftClick;private boolean mRightClick;public void sendLeftClickboolean click) {mLeftClick = click;senMousebyte) 0x00, byte) 0x00);}public void sendRightClickboolean click) {mRightClick = click;senMousebyte) 0x00, byte) 0x00);}public void senMousebyte dx, byte dy) {if mHidDevice == null) {Log.eTAG, "senMouse failed, hid device is null!");return;}if mHostDevice == null) {Log.eTAG, "senMouse failed, hid device is not connected!");return;}byte[] bytes = new byte[5];//bytes[0]字节:bit0: 1表示左键按下 0表示左键抬起 | bit1: 1表示右键按下 0表示右键抬起 | bit2: 1表示中键按下 | bit7~3:补充的常数,无意义,这里为0即可bytes[0] = byte) bytes[0] | mLeftClick ? 1 : 0));bytes[0] = byte) bytes[0] | mRightClick ? 1 : 0) << 1);bytes[1] = dx;bytes[2] = dy;Log.dTAG, "senMouse Left:" + mLeftClick+ ",Right:" + mRightClick + ",bytes: " + BluetoothUtils.bytesToHexStringbytes));mHidDevice.sendReportmHostDevice, 4, bytes);}public void sendWheelbyte hWheel, byte vWheel) {if mHidDevice == null) {Log.eTAG, "sendWheel failed, hid device is null!");return;}if mHostDevice == null) {Log.eTAG, "sendWheel failed, hid device is not connected!");return;}byte[] bytes = new byte[5];bytes[3] = vWheel; //垂直滚轮bytes[4] = hWheel; //水平滚轮Log.dTAG, "sendWheel vWheel:" + vWheel + ",hWheel:" + hWheel);mHidDevice.sendReportmHostDevice, 4, bytes);}
效果
实现以上步骤即可将手机变成蓝牙鼠标/触控板,下面是实现的效果:
蓝牙HID——将android设备变成蓝牙鼠标/触控板
查看全文
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dgrt.cn/a/230099.html
如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!
相关文章:
蓝牙HID——将android设备变成蓝牙鼠标/触控板(BluetoothHidDevice)
前言
本篇为蓝牙HID系列篇章之一,本篇以红米K30(MIUI13即Android 12)手机作为蓝牙HID设备,可以与电脑、手机、平板等其他蓝牙主机进行配对从而实现鼠标触控板的功能。 蓝牙HID系列篇章: 蓝牙HID——将android设备变成……
基于蚁群算法的TPS问题求解策略研究(Matlab代码实现)
🍒🍒🍒欢迎关注🌈🌈🌈 📝个人主页:我爱Matlab 👍点赞➕评论➕收藏 养成习惯(一键三连)🌻🌻🌻 🍌希……
【数据结构】——单链表
目录
1.链表
1.1 链表的概念及结构
1.2 链表的分类
1. 单向或者双向 2. 带头或者不带头 3. 循环或者非循环 1.3实现一个单链表(无头单项非循环链表增删查改的实现)
1.链表结构的创建
2.创建一个节点
3.创建一个链表
4.打印链表
5……
虚拟机Vmware)磁盘扩容(xfs格式)
先将虚拟机关机,按上图调整虚拟磁盘大小。 1.开启并进入虚拟机,打开终端,输入命令 df -Th 查看格式,图示中 /dev/mapper/centos-root 类型为xfs。
[mangolocalhost ~]$ df -Th
Filesystem Type Size Used Ava……
SpringFramework:SpringBean的注入方式
SpringBean的注入方式 文章目录SpringBean的注入方式一、Spring 容器1. 什么是容器2. 容器如何工作二、SpringBean 注入方式1. SpringBean 注入方式分类2. Autowiring 自动绑定三、获取 Spring Bean一、Spring 容器
1. 什么是容器
Spring IOC 容器就是一个 org.springframewo……
BurpSuit官方实验室之信息泄露
BurpSuit官方实验室之信息泄露
这是BurpSuit官方的实验室靶场,以下将记录个人信息泄露共5个Lab的通关过程
Web Security Academy: Free Online Training from PortSwigger
lab1:
Information disclosure in error messages
错误消息中的信息泄露
在……
27服务-安全访问状态转换
诊断协议那些事儿
诊断协议那些事儿专栏系列文章,本文将介绍安全访问状态图——作为UDS27服务的规范性附件。
可参考前两篇文章: 27服务-SecurityAccess UDS – 深论Security Access Service
27服务的初衷就是防止无权限人员进行非法数据操作ÿ……
JavaScript内置对象总结介绍
目录
浏览器对象模型
(1)值属性
(2)函数属性
(3)基本对象
(4)错误对象
(5)数字和日期对象
(6)字符串
(7ÿ……
brew安装特定版本flow,解决问题!
在atomreact-native项目中安装了nuclide。然而使用flow的时候出现了问题。
$ brew -v
Homebrew 1.2.0$ flow version
Flow, a static type checker for JavaScript, version 0.45.0$ flow
Launching Flow server for /Users/real/Desktop/reactPro/pro2
Wrong version of Flow……
开源作品:引流宝!集活码、短网址等功能为一体的工具!致力于提高引流效率,减少资源流失!
前言
开发这款工具的初衷是为了辅助自己的工作,提供自己日常工作的效率,自己使用了一段时间下来觉得很有用,于是完善之后开源。如今已经开源近2年,第一个版本是在2020年9月份开源,收获了390个star,后来持续……
数据要素化条件之一:原始性
随着技术的发展,计算机不仅成为人类处理信息的工具,而且逐渐地具有自主处理数据的能力,出现了替代人工的数据智能技术。数据智能的大规模使用需要关于同一分析对象或同一问题的、来源于不同数据源的海量数据。这种数据必须是针对特定对象的记……
【面试题 高逼格利用 类实现加法】编写代码, 实现多线程数组求和.
编写代码, 实现多线程数组求和.关键1. 数组的初始化关键2. 奇偶的相加import java.util.Random;public class Thread_2533 {public static void mainString[] args) throws InterruptedException {// 记录开始时间long start System.currentTimeMillis);// 1. 给定一个很长的……
一个python训练
美国:28:麻省理工学院,斯坦福大学,哈佛大学,加州理工学院,芝加哥大学,普林斯顿大学,宾夕法尼亚大学,耶鲁大学,康奈尔大学,哥伦比亚大学,密歇根大学安娜堡分校,约翰霍普金斯大学,西北大学,加州大学伯克利分校,纽约大学,加州大学洛杉矶分校,杜克大学,卡内基梅隆大学,加州大学圣地……
Mybatis03学习笔记
目录 使用注解开发
设置事务自动提交
mybatis运行原理
注解CRUD
lombok使用(偷懒神器,大神都不建议使用)
复杂查询环境(多对一)
复杂查询环境(一对多)
动态sql环境搭建
动态sql常用标签……
编程日记2023/4/16 14:55:50
设置或取得c# NumericUpDown 编辑框值的方法,注意:不是Value值)
本人在C#开发中使用到了NumericUpDown控件,但是发现该控件不能直接控制显示值,经研究得到下面的解决办法
NumericUpDown由于是由多个控件组合而来的控件,其中包含一个类似TextBox的控件,若想取得或改变其中的值要使用如下方法
N……
编程日记2023/4/16 14:55:46
使用NPOI 技术 的SetColumnWidth 精确控制列宽不能成功的解决办法(C#)
在使用NPOI技术开发自动操作EXCEL软件时遇到不能精确设置列宽的问题。
如
ISheet sheet1 hssfworkbook.CreateSheet"Sheet1");
sheet1.SetColumnWidth0, 50 * 256); // 在EXCEL文档中实际列宽为49.29
sheet1.SetColumnWidth1, 100 * 256); // 在EXCEL文……
编程日记2023/4/16 14:55:46
Mysql 数据库zip版安装时basedir datadir 路径设置问题,避免转义符的影响
本人在开发Mysql数据库自动安装程序时遇到个很奇怪的问题,其中my.ini的basedir 的路径设置是下面这样的:
basedir d:\测试\test\mysql
但是在使用mysqld安装mysql服务时老是启动不了,报1067错误,后来查看window事件发现一个独特……
java stream sorted排序 考虑null值
项目里使用到排序, java里没有像C# 里的linq,只有stream,查找stream.sorted源码看到有个
Comparator.nullsLast
然后看了一下实现,果然是能够处理null值的排序,如:minPriceList.stream).sortedComparator.comparingl -> l.g……
spring @EnableConfigurationProperties 实现原理
查看DataSourceAutoConfiguration源码,发现如下代码: Configuration ConditionalOnClass{ DataSource.class, EmbeddedDatabaseType.class }) EnableConfigurationPropertiesDataSourceProperties.class) Import{ DataSourcePoolMetadataProvidersCon……
postman请求https网址没有响应,但是用浏览器有响应,解决办法
遇到个问题:同一个get请求的url,postman请求https网址没有响应,但是用浏览器有响应
url是https开头的,查看错误描述里有一个SSL的选项: 然后根据描述关掉这个选项: 然后就没问题了,能正常请求及……
编程日记2023/4/16 14:55:44