本篇為藍牙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 * 的問題分析
Android 9開放了 BluetoothHidDevice 等HID相關的API,通過與系統藍牙HID服務通信注冊成藍牙HID設備。首先通過 BluetoothProfile.HID_DEVICE 的描述類型得到 BluetoothHidDevice 抽象實例:
private BluetoothAdapter mBtAdapter;private BluetoothHidDevice mHidDevice;private void callBluetooth() {Log.d(TAG, "callBluetooth");mBtAdapter = BluetoothAdapter.getDefaultAdapter();mBtAdapter.getProfileProxy(mContext, new BluetoothProfile.ServiceListener() {@Overridepublic void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {Log.d(TAG, "onServiceConnected:" + i);if (i == BluetoothProfile.HID_DEVICE) {if (!(bluetoothProfile instanceof BluetoothHidDevice)) {Log.e(TAG, "Proxy received but it's not BluetoothHidDevice");return;}mHidDevice = (BluetoothHidDevice) bluetoothProfile;registerBluetoothHid();}}@Overridepublic void onServiceDisconnected(int i) {Log.d(TAG, "onServiceDisconnected:" + i);}}, BluetoothProfile.HID_DEVICE);}再調用 BluetoothHidDevice.registerApp() 將 Android 設備注冊成藍牙HID設備:
private BluetoothDevice mHostDevice;private final BluetoothHidDeviceAppQosSettings qosSettings= new BluetoothHidDeviceAppQosSettings(BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,800, 9, 0, 11250, BluetoothHidDeviceAppQosSettings.MAX);private final BluetoothHidDeviceAppSdpSettings mouseSdpSettings = new BluetoothHidDeviceAppSdpSettings(HidConfig.MOUSE_NAME, HidConfig.DESCRIPTION, HidConfig.PROVIDER,BluetoothHidDevice.SUBCLASS1_MOUSE, HidConfig.MOUSE_COMBO);private void registerBluetoothHid() {if (mHidDevice == null) {Log.e(TAG, "hid device is null");return;}mHidDevice.registerApp(mouseSdpSettings, null, qosSettings, Executors.newCachedThreadPool(), new BluetoothHidDevice.Callback() {@Overridepublic void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {Log.d(TAG, "onAppStatusChanged:" + (pluggedDevice != null ? pluggedDevice.getName() : "null") + " registered:" + registered);if (registered) {Log.d(TAG, "paired devices: " + mHidDevice.getConnectionState(pluggedDevice));if (pluggedDevice != null && mHidDevice.getConnectionState(pluggedDevice) != BluetoothProfile.STATE_CONNECTED) {boolean result = mHidDevice.connect(pluggedDevice);Log.d(TAG, "hidDevice connect:" + result);}}if (mBluetoothHidStateListener != null) {mBluetoothHidStateListener.onRegisterStateChanged(registered, pluggedDevice != null);}}@Overridepublic void onConnectionStateChanged(BluetoothDevice device, int state) {Log.d(TAG, "onConnectionStateChanged:" + device + " state:" + state);if (state == BluetoothProfile.STATE_CONNECTED) {mHostDevice = device;}if (state == BluetoothProfile.STATE_DISCONNECTED) {mHostDevice = null;}if (mBluetoothHidStateListener != null) {mBluetoothHidStateListener.onConnectionStateChanged(state);}}});}藍牙鼠標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_COLLECTION(byte) 0xc0, // END_COLLECTION};在注冊完成后啟動設備發現,讓HID能被其他設備發現,下面ActivityResultLauncher.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE) 相當于調用 BluetoothAdapter.setScanMode() 的隱藏API
private ActivityResultLauncher<Intent> mActivityResultLauncher;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_mouse);mActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {Log.d(TAG, "onActivityResult:" + result.toString());});}@Overridepublic void onRegisterStateChanged(boolean registered, boolean hasDevice) {if (registered) {if (!hasDevice) {// startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), 1);mActivityResultLauncher.launch(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE));}}}ActivityResultLauncher 的相關方法也可用 startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), REQUEST_CODE) 來替代,但 startActivityForResult() 是廢棄的方法,不建議使用。
接下來與藍牙主機(電腦、手機等)進行藍牙配對,已配對過需要取消配對。配對完成即可實現對藍牙主機的鼠標觸摸控制。
手勢識別通過對觸摸事件以及手勢監聽進行各種手勢的判斷(移動鼠標、左鍵單擊、左鍵雙擊、右鍵雙指單擊、雙指垂直/水平滾動)。
CustomMotionListener customMotionListener = new CustomMotionListener(this, mBluetoothHidManager); findViewById(R.id.layout_touch).setOnTouchListener(customMotionListener);手勢邏輯處理代碼如下:
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 CustomMotionListener(Context context, BluetoothHidManager bluetoothHidManager) {mBluetoothHidManager = bluetoothHidManager;mGestureDetector = new GestureDetector(context, this);mGestureDetector.setOnDoubleTapListener(this);mExecutorService = new ScheduledThreadPoolExecutor(1,new BasicThreadFactory.Builder().namingPattern("mouse-schedule-pool-%d").daemon(true).build());}@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {return false;}@Overridepublic boolean onDoubleTap(MotionEvent e) {return false;}@Overridepublic boolean onDoubleTapEvent(MotionEvent e) {//左鍵單指雙擊(選中文本的效果)if (e.getAction() == MotionEvent.ACTION_DOWN) {mBluetoothHidManager.sendLeftClick(true);} else if (e.getAction() == MotionEvent.ACTION_UP) {mBluetoothHidManager.sendLeftClick(false);}return true;}@Overridepublic boolean onDown(MotionEvent e) {return false;}@Overridepublic void onShowPress(MotionEvent e) {}@Overridepublic boolean onSingleTapUp(MotionEvent e) {//左鍵單擊mBluetoothHidManager.sendLeftClick(true);mBluetoothHidManager.sendLeftClick(false);return true;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//雙指滾動,x為水平滾動,y為垂直滾動,消抖處理if (mPointCount == 2) {if (Math.abs(distanceX) > Math.abs(distanceY)) {distanceX = distanceX > 0 ? 1 : distanceX < 0 ? -1 : 0;distanceY = 0;} else {distanceY = distanceY > 0 ? -1 : distanceY < 0 ? 1 : 0;distanceX = 0;}mBluetoothHidManager.sendWheel((byte) (distanceX), (byte) (distanceY));}return false;}@Overridepublic void onLongPress(MotionEvent e) {//單鍵長按效果mBluetoothHidManager.sendLeftClick(true);mLongPress = true;}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {return false;}@Overridepublic boolean onTouch(View v, MotionEvent event) {float x = event.getX();float y = event.getY();if (mGestureDetector.onTouchEvent(event)) {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.senMouse((byte) dx, (byte) dy);} else {mBluetoothHidManager.senMouse((byte) 0, (byte) 0);}break;case MotionEvent.ACTION_UP:if (mLongPress) {mBluetoothHidManager.sendLeftClick(false);mLongPress = false;}break;case MotionEvent.ACTION_POINTER_UP://雙指按下代表右鍵if (event.getPointerCount() == 2 && System.currentTimeMillis() - mDoubleFingerTime < ViewConfiguration.getDoubleTapTimeout()) {mBluetoothHidManager.sendRightClick(true);//延時釋放避免無效mExecutorService.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {mBluetoothHidManager.sendRightClick(false);}}, 0, 50, TimeUnit.MILLISECONDS); }break;default:break;}mPreX = x;mPreY = y;return true;} }向藍牙主機發送的鼠標觸摸按鍵的報告如下:
private boolean mLeftClick;private boolean mRightClick;public void sendLeftClick(boolean click) {mLeftClick = click;senMouse((byte) 0x00, (byte) 0x00);}public void sendRightClick(boolean click) {mRightClick = click;senMouse((byte) 0x00, (byte) 0x00);}public void senMouse(byte dx, byte dy) {if (mHidDevice == null) {Log.e(TAG, "senMouse failed, hid device is null!");return;}if (mHostDevice == null) {Log.e(TAG, "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.d(TAG, "senMouse Left:" + mLeftClick+ ",Right:" + mRightClick + ",bytes: " + BluetoothUtils.bytesToHexString(bytes));mHidDevice.sendReport(mHostDevice, 4, bytes);}public void sendWheel(byte hWheel, byte vWheel) {if (mHidDevice == null) {Log.e(TAG, "sendWheel failed, hid device is null!");return;}if (mHostDevice == null) {Log.e(TAG, "sendWheel failed, hid device is not connected!");return;}byte[] bytes = new byte[5];bytes[3] = vWheel; //垂直滾輪bytes[4] = hWheel; //水平滾輪Log.d(TAG, "sendWheel vWheel:" + vWheel + ",hWheel:" + hWheel);mHidDevice.sendReport(mHostDevice, 4, bytes);}實現以上步驟即可將手機變成藍牙鼠標/觸控板,下面是實現的效果:
鼠標移動:
左鍵單擊:
左鍵單指快速雙擊:
右鍵雙指單擊:
雙指垂直上下滾動:
雙指水平左右滾動:
完整視頻效果展示:
藍牙HID——將android設備變成藍牙鼠標/觸控板
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
有沒有人花888充值淘寶88會員?懂的人說說怎么樣吧?過來看看,有個傻女孩[羅斯],因為她一年前是88kai的會員,但續約時沒注意。當時規定1000分以下需要888分。我沒注意[遮住我的臉]。當我發現的時候,它已經在支付界面上了。當時,我的手機是華為榮耀。返回按鈕和指紋支付按鈕是相同的。當我想按返回按鈕時,付款成功了。后來聯系淘寶客服,他們還給我了??头χf不相信有人會買888。初衷是讓大家有一...
北京到銀川的所有火車?北京到銀川有兩趟車,其中一趟是過路車。1.K177北京西到銀川。西站發車時間為下午13:25。硬座143(全價),臥鋪262。它總共運行了大約19個小時。值得一提的是,這趟車的目的地是直達銀川,到達時間是第二天早上8: 30左右。2.K43這班車分A、b兩種,每天從嘉峪關、蘭州出發,到達目的地第二天就到了蘭州(嘉峪關),但不管怎么說,都是去銀川的。北京站發車時間12: 05,...
有哪些實用的學習類App?10個讓我相見恨晚的學習app,我可以 不停的看app,既能學到知識,又能拓展我的思維,實用,小眾的寶藏APP。主要分為三種,學習,拓展知識,工具,都是干貨。學習類| 1。今天背的單詞,過幾天就忘了。背單詞很費時間,效率很低。所以墨墨背單詞是我強烈推薦的一款APP。他可以分析你的遺忘曲線,定位每個單詞的遺忘臨界點,調整復習計劃,更高效的記憶單詞。最后,我不 I don‘我...