summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Himanshu Rawat <rwt@google.com> 2024-08-02 17:27:51 +0000
committer Gerrit Code Review <noreply-gerritcodereview@google.com> 2024-08-02 17:27:51 +0000
commit2110c70f2e8b2c49ba7fdffa64b8afcf20b06c2f (patch)
treebd11a1d8e22be01cd9f09cc310f74870ef0aaffd
parentcb5774b5b057de01a6b1aad919ac272d0caa0d19 (diff)
parent43845b5f375df72bf4a559b033e6ada15a029ede (diff)
Merge changes Idabb8939,Ibbd8e9a1,If246e311,I788c1f6b,I76858fd8 into main
* changes: Bumble Java HID Test cases Bumble Java HID Test cases Bumble test infra changes Bumble Java HID Test cases Bumble Java HID Test cases
-rw-r--r--framework/tests/bumble/AndroidTest.xml1
-rw-r--r--framework/tests/bumble/src/android/bluetooth/hid/HidHostDualModeTest.java410
-rw-r--r--framework/tests/bumble/src/android/bluetooth/hid/HidHostTest.java354
-rw-r--r--pandora/interfaces/pandora_experimental/hid.proto26
-rw-r--r--pandora/server/bumble_experimental/hid.py89
5 files changed, 813 insertions, 67 deletions
diff --git a/framework/tests/bumble/AndroidTest.xml b/framework/tests/bumble/AndroidTest.xml
index 1b31b827e2..4855fec134 100644
--- a/framework/tests/bumble/AndroidTest.xml
+++ b/framework/tests/bumble/AndroidTest.xml
@@ -42,6 +42,7 @@
<option name="test-tag" value="BumbleBluetoothTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.bluetooth" />
+ <option name="hidden-api-checks" value="false" />
</test>
<!-- Only run if the Bluetooth Mainline module is installed. -->
diff --git a/framework/tests/bumble/src/android/bluetooth/hid/HidHostDualModeTest.java b/framework/tests/bumble/src/android/bluetooth/hid/HidHostDualModeTest.java
new file mode 100644
index 0000000000..9cdd8f9c29
--- /dev/null
+++ b/framework/tests/bumble/src/android/bluetooth/hid/HidHostDualModeTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.bluetooth.flags.Flags;
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import pandora.HIDGrpc;
+import pandora.HostProto.AdvertiseRequest;
+import pandora.HostProto.OwnAddressType;
+
+/** Test cases for {@link Hid Host}. */
+@RunWith(AndroidJUnit4.class)
+public class HidHostDualModeTest {
+ private static final String TAG = "HidHostDualModeTest";
+ private SettableFuture<Integer> mFutureConnectionIntent,
+ mFutureBondIntent,
+ mFutureHandShakeIntent,
+ mFutureReportIntent,
+ mFutureProtocolModeIntent,
+ mFutureTransportIntent;
+ private SettableFuture<Boolean> mFutureHogpServiceIntent;
+ private BluetoothDevice mDevice;
+ private BluetoothHidHost mHidService;
+ private BluetoothHeadset mHfpService;
+ private BluetoothA2dp mA2dpService;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class);
+ private final BluetoothAdapter mAdapter = mManager.getAdapter();
+ private HIDGrpc.HIDBlockingStub mHidBlockingStub;
+ private byte mReportId;
+ private static final int KEYBD_RPT_ID = 1;
+ private static final int KEYBD_RPT_SIZE = 9;
+ private static final int MOUSE_RPT_ID = 2;
+ private static final int MOUSE_RPT_SIZE = 4;
+
+ @Rule(order = 0)
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule(order = 1)
+ public final AdoptShellPermissionsRule mPermissionRule = new AdoptShellPermissionsRule();
+
+ @Rule(order = 2)
+ public final PandoraDevice mBumble = new PandoraDevice();
+
+ private BroadcastReceiver mHidStateReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED:
+ int state =
+ intent.getIntExtra(
+ BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
+ int transport =
+ intent.getIntExtra(
+ BluetoothDevice.EXTRA_TRANSPORT,
+ BluetoothDevice.TRANSPORT_AUTO);
+ Log.i(
+ TAG,
+ "Connection state change: "
+ + state
+ + "transport: "
+ + transport);
+ if (state == BluetoothProfile.STATE_CONNECTED
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ if (mFutureConnectionIntent != null) {
+ mFutureConnectionIntent.set(state);
+ }
+ if (state == BluetoothProfile.STATE_CONNECTED
+ && mFutureTransportIntent != null) {
+ mFutureTransportIntent.set(transport);
+ }
+ }
+ break;
+ case BluetoothDevice.ACTION_PAIRING_REQUEST:
+ mBumble.getRemoteDevice().setPairingConfirmation(true);
+ break;
+ case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+ int bondState =
+ intent.getIntExtra(
+ BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ Log.i(TAG, "Bond state change:" + bondState);
+ if (bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_NONE) {
+ if (mFutureBondIntent != null) {
+ mFutureBondIntent.set(bondState);
+ }
+ }
+ break;
+ case BluetoothDevice.ACTION_UUID:
+ ParcelUuid[] parcelUuids =
+ intent.getParcelableArrayExtra(
+ BluetoothDevice.EXTRA_UUID, ParcelUuid.class);
+ for (int i = 0; i < parcelUuids.length; i++) {
+ Log.d(TAG, "UUIDs : index=" + i + " uuid=" + parcelUuids[i]);
+ if (parcelUuids[i].equals(BluetoothUuid.HOGP)) {
+ if (mFutureHogpServiceIntent != null) {
+ mFutureHogpServiceIntent.set(true);
+ }
+ }
+ }
+ break;
+ case BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED:
+ int protocolMode =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_PROTOCOL_MODE,
+ BluetoothHidHost.PROTOCOL_UNSUPPORTED_MODE);
+ Log.i(TAG, "Protocol mode:" + protocolMode);
+ if (mFutureProtocolModeIntent != null) {
+ mFutureProtocolModeIntent.set(protocolMode);
+ }
+ break;
+ case BluetoothHidHost.ACTION_HANDSHAKE:
+ int handShake =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_STATUS,
+ BluetoothHidDevice.ERROR_RSP_UNKNOWN);
+ Log.i(TAG, "Handshake status:" + handShake);
+ if (mFutureHandShakeIntent != null) {
+ mFutureHandShakeIntent.set(handShake);
+ }
+ break;
+ case BluetoothHidHost.ACTION_REPORT:
+ byte[] report = intent.getByteArrayExtra(BluetoothHidHost.EXTRA_REPORT);
+ int reportSize =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, 0);
+ mReportId = report[0];
+ if (mFutureReportIntent != null) {
+ mFutureReportIntent.set((reportSize - 1));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ // These callbacks run on the main thread.
+ private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ switch (profile) {
+ case BluetoothProfile.HEADSET:
+ mHfpService = (BluetoothHeadset) proxy;
+ break;
+ case BluetoothProfile.A2DP:
+ mA2dpService = (BluetoothA2dp) proxy;
+ break;
+ case BluetoothProfile.HID_HOST:
+ mHidService = (BluetoothHidHost) proxy;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {}
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
+ filter.addAction(BluetoothDevice.ACTION_UUID);
+ filter.addAction(BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED);
+ filter.addAction(BluetoothHidHost.ACTION_HANDSHAKE);
+ filter.addAction(BluetoothHidHost.ACTION_REPORT);
+ mContext.registerReceiver(mHidStateReceiver, filter);
+ mAdapter.getProfileProxy(
+ mContext, mBluetoothProfileServiceListener, BluetoothProfile.HID_HOST);
+ mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
+ mAdapter.getProfileProxy(
+ mContext, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+ mHidBlockingStub = mBumble.hidBlocking();
+ AdvertiseRequest request =
+ AdvertiseRequest.newBuilder()
+ .setLegacy(true)
+ .setConnectable(true)
+ .setOwnAddressType(OwnAddressType.RANDOM)
+ .build();
+ mBumble.hostBlocking().advertise(request);
+
+ mFutureConnectionIntent = SettableFuture.create();
+
+ mDevice = mBumble.getRemoteDevice();
+ assertThat(mDevice.createBond()).isTrue();
+ assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ if (mA2dpService != null) {
+ assertThat(
+ mA2dpService.setConnectionPolicy(
+ mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN))
+ .isTrue();
+ }
+ if (mHfpService != null) {
+ assertThat(
+ mHfpService.setConnectionPolicy(
+ mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN))
+ .isTrue();
+ }
+ mFutureHogpServiceIntent = SettableFuture.create();
+ assertThat(mFutureHogpServiceIntent.get()).isTrue();
+ assertThat(mHidService.getPreferredTransport(mDevice))
+ .isEqualTo(BluetoothDevice.TRANSPORT_BREDR);
+ // LE transport
+ mFutureTransportIntent = SettableFuture.create();
+ mHidService.setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_LE);
+ // Verifies BREDR transport Disconnected
+ mFutureConnectionIntent = SettableFuture.create();
+ assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+
+ assertThat(mFutureTransportIntent.get()).isEqualTo(BluetoothDevice.TRANSPORT_LE);
+ assertThat(mHidService.getPreferredTransport(mDevice))
+ .isEqualTo(BluetoothDevice.TRANSPORT_LE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+ mFutureBondIntent = SettableFuture.create();
+ mDevice.removeBond();
+ assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE);
+ }
+ mContext.unregisterReceiver(mHidStateReceiver);
+ }
+
+ /**
+ * Test HID Preferred transport selection Test case
+ *
+ * <ol>
+ * <li>1. Android to creates bonding and HID connected with default transport.
+ * <li>2. Android switch the transport to LE and Verifies the transport
+ * <li>3. Android switch the transport to BR/EDR and Verifies the transport
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void setPreferredTransportTest() throws Exception {
+
+ // BREDR transport
+ mFutureTransportIntent = SettableFuture.create();
+ mHidService.setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_BREDR);
+ // Verifies LE transport Disconnected
+ mFutureConnectionIntent = SettableFuture.create();
+ assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+
+ assertThat(mFutureTransportIntent.get()).isEqualTo(BluetoothDevice.TRANSPORT_BREDR);
+ assertThat(mHidService.getPreferredTransport(mDevice))
+ .isEqualTo(BluetoothDevice.TRANSPORT_BREDR);
+ }
+
+ /**
+ * Test Get Report
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android get report and verifies the report
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void hogpGetReportTest() throws Exception {
+
+ // Keyboard report
+ byte id = KEYBD_RPT_ID;
+ mHidService.getReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, id, (int) 0);
+ mFutureReportIntent = SettableFuture.create();
+ assertThat(mFutureReportIntent.get()).isEqualTo(KEYBD_RPT_SIZE);
+ assertThat(mReportId).isEqualTo(KEYBD_RPT_ID);
+
+ // Mouse report
+ id = MOUSE_RPT_ID;
+ mHidService.getReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, id, (int) 0);
+ mFutureReportIntent = SettableFuture.create();
+ assertThat(mFutureReportIntent.get()).isEqualTo(MOUSE_RPT_SIZE);
+ assertThat(mReportId).isEqualTo(MOUSE_RPT_ID);
+ }
+
+ /**
+ * Test Get Protocol mode
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Gets the Protocol mode and verifies the mode
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void hogpGetProtocolModeTest() throws Exception {
+ mHidService.getProtocolMode(mDevice);
+ mFutureProtocolModeIntent = SettableFuture.create();
+ assertThat(mFutureProtocolModeIntent.get())
+ .isEqualTo(BluetoothHidHost.PROTOCOL_REPORT_MODE);
+ }
+
+ /**
+ * Test Set Protocol mode
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Sets the Protocol mode and verifies the mode
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void hogpSetProtocolModeTest() throws Exception {
+ mHidService.setProtocolMode(mDevice, BluetoothHidHost.PROTOCOL_BOOT_MODE);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get()).isEqualTo(BluetoothHidDevice.ERROR_RSP_SUCCESS);
+ }
+
+ /**
+ * Test Set Report
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Set report and verifies the report
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void hogpSetReportTest() throws Exception {
+ // Keyboard report
+ mHidService.setReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, "010203040506070809");
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get()).isEqualTo(BluetoothHidDevice.ERROR_RSP_SUCCESS);
+ // Mouse report
+ mHidService.setReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, "02030405");
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get()).isEqualTo(BluetoothHidDevice.ERROR_RSP_SUCCESS);
+ }
+
+ /**
+ * Test Virtual Unplug from Hid Host
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Virtual Unplug and verifies Bonding
+ * </ol>
+ */
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP,
+ Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY
+ })
+ public void hogpVirtualUnplugFromHidHostTest() throws Exception {
+ mHidService.virtualUnplug(mDevice);
+ mFutureBondIntent = SettableFuture.create();
+ assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE);
+ }
+}
diff --git a/framework/tests/bumble/src/android/bluetooth/hid/HidHostTest.java b/framework/tests/bumble/src/android/bluetooth/hid/HidHostTest.java
index 4166d4b3e2..2a2a20ef18 100644
--- a/framework/tests/bumble/src/android/bluetooth/hid/HidHostTest.java
+++ b/framework/tests/bumble/src/android/bluetooth/hid/HidHostTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import pandora.HIDGrpc;
+import pandora.HidProto.ProtocolModeEvent;
+import pandora.HidProto.ReportEvent;
+import java.time.Duration;
+import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -55,7 +59,11 @@ public class HidHostTest {
private static final String TAG = "HidHostTest";
private SettableFuture<Integer> mFutureConnectionIntent,
mFutureAdapterStateIntent,
- mFutureBondIntent;
+ mFutureBondIntent,
+ mFutureHandShakeIntent,
+ mFutureProtocolModeIntent,
+ mFutureVirtualUnplugIntent,
+ mFutureReportIntent;
private SettableFuture<Boolean> mAclConnectionIntent;
private BluetoothDevice mDevice;
private BluetoothHidHost mHidService;
@@ -65,8 +73,16 @@ public class HidHostTest {
private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class);
private final BluetoothAdapter mAdapter = mManager.getAdapter();
private HIDGrpc.HIDBlockingStub mHidBlockingStub;
+ private byte mReportId;
+ private static final int KEYBD_RPT_ID = 1;
+ private static final int KEYBD_RPT_SIZE = 9;
+ private static final int MOUSE_RPT_ID = 2;
+ private static final int MOUSE_RPT_SIZE = 4;
+ private static final int INVALID_RPT_ID = 3;
private static final int CONNECTION_TIMEOUT_MS = 2_000;
+ private static final Duration PROTO_MODE_TIMEOUT = Duration.ofSeconds(10);
+
@Rule(order = 0)
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -76,51 +92,98 @@ public class HidHostTest {
@Rule(order = 2)
public final PandoraDevice mBumble = new PandoraDevice();
- private BroadcastReceiver mConnectionStateReceiver =
+ private BroadcastReceiver mHidStateReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED.equals(
- intent.getAction())) {
- int state =
- intent.getIntExtra(
- BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
- Log.i(TAG, "Connection state change:" + state);
- if (state == BluetoothProfile.STATE_CONNECTED
- || state == BluetoothProfile.STATE_DISCONNECTED) {
- if (mFutureConnectionIntent != null) {
- mFutureConnectionIntent.set(state);
+ switch (intent.getAction()) {
+ case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED:
+ int state =
+ intent.getIntExtra(
+ BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
+ Log.i(TAG, "Connection state change:" + state);
+ if (state == BluetoothProfile.STATE_CONNECTED
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ if (mFutureConnectionIntent != null) {
+ mFutureConnectionIntent.set(state);
+ }
+ }
+ break;
+ case BluetoothDevice.ACTION_PAIRING_REQUEST:
+ mBumble.getRemoteDevice().setPairingConfirmation(true);
+ break;
+ case BluetoothAdapter.ACTION_STATE_CHANGED:
+ int adapterState =
+ intent.getIntExtra(
+ BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
+ Log.i(TAG, "Adapter state change:" + adapterState);
+ if (adapterState == BluetoothAdapter.STATE_ON
+ || adapterState == BluetoothAdapter.STATE_OFF) {
+ if (mFutureAdapterStateIntent != null) {
+ mFutureAdapterStateIntent.set(adapterState);
+ }
}
- }
- } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
- mBumble.getRemoteDevice().setPairingConfirmation(true);
- } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
- int adapterState =
- intent.getIntExtra(
- BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- Log.i(TAG, "Adapter state change:" + adapterState);
- if (adapterState == BluetoothAdapter.STATE_ON
- || adapterState == BluetoothAdapter.STATE_OFF) {
- if (mFutureAdapterStateIntent != null) {
- mFutureAdapterStateIntent.set(adapterState);
+ break;
+ case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+ int bondState =
+ intent.getIntExtra(
+ BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ Log.i(TAG, "Bond state change:" + bondState);
+ if (bondState == BluetoothDevice.BOND_BONDED
+ || bondState == BluetoothDevice.BOND_NONE) {
+ if (mFutureBondIntent != null) {
+ mFutureBondIntent.set(bondState);
+ }
}
- }
- } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(
- intent.getAction())) {
- int bondState =
- intent.getIntExtra(
- BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
- Log.i(TAG, "Bond state change:" + bondState);
- if (bondState == BluetoothDevice.BOND_BONDED
- || bondState == BluetoothDevice.BOND_NONE) {
- if (mFutureBondIntent != null) {
- mFutureBondIntent.set(bondState);
+ break;
+ case BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED:
+ int protocolMode =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_PROTOCOL_MODE,
+ BluetoothHidHost.PROTOCOL_UNSUPPORTED_MODE);
+ Log.i(TAG, "Protocol mode:" + protocolMode);
+ if (mFutureProtocolModeIntent != null) {
+ mFutureProtocolModeIntent.set(protocolMode);
}
- }
- } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) {
- if (mAclConnectionIntent != null) {
- mAclConnectionIntent.set(true);
- }
+ break;
+ case BluetoothHidHost.ACTION_HANDSHAKE:
+ int handShake =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_STATUS,
+ BluetoothHidDevice.ERROR_RSP_UNKNOWN);
+ Log.i(TAG, "Handshake status:" + handShake);
+ if (mFutureHandShakeIntent != null) {
+ mFutureHandShakeIntent.set(handShake);
+ }
+ break;
+ case BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS:
+ int virtualUnplug =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_VIRTUAL_UNPLUG_STATUS,
+ BluetoothHidHost.VIRTUAL_UNPLUG_STATUS_FAIL);
+ Log.i(TAG, "Virtual Unplug status:" + virtualUnplug);
+ if (mFutureVirtualUnplugIntent != null) {
+ mFutureVirtualUnplugIntent.set(virtualUnplug);
+ }
+ break;
+ case BluetoothHidHost.ACTION_REPORT:
+ byte[] report = intent.getByteArrayExtra(BluetoothHidHost.EXTRA_REPORT);
+ int reportSize =
+ intent.getIntExtra(
+ BluetoothHidHost.EXTRA_REPORT_BUFFER_SIZE, 0);
+ mReportId = report[0];
+ if (mFutureReportIntent != null) {
+ mFutureReportIntent.set((reportSize - 1));
+ }
+ break;
+ case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+ if (mAclConnectionIntent != null) {
+ mAclConnectionIntent.set(true);
+ }
+ break;
+ default:
+ break;
}
}
};
@@ -152,17 +215,18 @@ public class HidHostTest {
@Before
public void setUp() throws Exception {
- mContext.registerReceiver(
- mConnectionStateReceiver,
- new IntentFilter(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED));
- mContext.registerReceiver(
- mConnectionStateReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST));
- mContext.registerReceiver(
- mConnectionStateReceiver,
- new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
- mContext.registerReceiver(
- mConnectionStateReceiver,
- new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothHidHost.ACTION_PROTOCOL_MODE_CHANGED);
+ filter.addAction(BluetoothHidHost.ACTION_HANDSHAKE);
+ filter.addAction(BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS);
+ filter.addAction(BluetoothHidHost.ACTION_REPORT);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+
+ mContext.registerReceiver(mHidStateReceiver, filter);
mAdapter.getProfileProxy(
mContext, mBluetoothProfileServiceListener, BluetoothProfile.HID_HOST);
mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
@@ -191,6 +255,7 @@ public class HidHostTest {
@After
public void tearDown() throws Exception {
+
if (mDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
mFutureBondIntent = SettableFuture.create();
mDevice.removeBond();
@@ -202,7 +267,8 @@ public class HidHostTest {
mDevice.disconnect();
assertThat(mAclConnectionIntent.get()).isTrue();
}
- mContext.unregisterReceiver(mConnectionStateReceiver);
+
+ mContext.unregisterReceiver(mHidStateReceiver);
}
/**
@@ -368,12 +434,183 @@ public class HidHostTest {
assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ mFutureBondIntent = SettableFuture.create();
mDevice.removeBond();
+ assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE);
- mFutureConnectionIntent = SettableFuture.create();
- mHidBlockingStub.connectHost(Empty.getDefaultInstance());
- assertThat(mHidService.getConnectionState(mDevice))
- .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ reconnectionFromRemoteAndVerifyDisconnectedState();
+ }
+
+ /**
+ * Test Virtual Unplug from Hid Host
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Virtual Unplug and verifies Bonding
+ * </ol>
+ */
+ @Test
+ public void hidVirtualUnplugFromHidHostTest() throws Exception {
+ mHidService.virtualUnplug(mDevice);
+ mFutureBondIntent = SettableFuture.create();
+ assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE);
+ }
+
+ /**
+ * Test Virtual Unplug from Hid Device
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Bumble Virtual Unplug and Android verifies Bonding
+ * </ol>
+ */
+ @Test
+ public void hidVirtualUnplugFromHidDeviceTest() throws Exception {
+ mHidBlockingStub.virtualCableUnplugHost(Empty.getDefaultInstance());
+ mFutureVirtualUnplugIntent = SettableFuture.create();
+ assertThat(mFutureVirtualUnplugIntent.get())
+ .isEqualTo(BluetoothHidHost.VIRTUAL_UNPLUG_STATUS_SUCCESS);
+ }
+
+ /**
+ * Test Get Protocol mode
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Gets the Protocol mode and verifies the mode
+ * </ol>
+ */
+ @Test
+ public void hidGetProtocolModeTest() throws Exception {
+ mHidService.getProtocolMode(mDevice);
+ mFutureProtocolModeIntent = SettableFuture.create();
+ assertThat(mFutureProtocolModeIntent.get())
+ .isEqualTo(BluetoothHidHost.PROTOCOL_REPORT_MODE);
+ }
+
+ /**
+ * Test Set Protocol mode
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Sets the Protocol mode and verifies the mode
+ * </ol>
+ */
+ @Test
+ @Ignore("b/349351673: sets wrong protocol mode value")
+ public void hidSetProtocolModeTest() throws Exception {
+ Iterator<ProtocolModeEvent> mHidProtoModeEventObserver =
+ mHidBlockingStub
+ .withDeadlineAfter(PROTO_MODE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
+ .onSetProtocolMode(Empty.getDefaultInstance());
+ mHidService.setProtocolMode(mDevice, BluetoothHidHost.PROTOCOL_BOOT_MODE);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get())
+ .isEqualTo(BluetoothHidDevice.ERROR_RSP_UNSUPPORTED_REQ);
+ if (mHidProtoModeEventObserver.hasNext()) {
+ ProtocolModeEvent hidProtoModeEvent = mHidProtoModeEventObserver.next();
+ Log.i(TAG, "Protocol mode:" + hidProtoModeEvent.getProtocolMode());
+ assertThat(hidProtoModeEvent.getProtocolModeValue())
+ .isEqualTo(BluetoothHidHost.PROTOCOL_BOOT_MODE);
+ }
+ }
+
+ /**
+ * Test Get Report
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android get report and verifies the report
+ * </ol>
+ */
+ @Test
+ public void hidGetReportTest() throws Exception {
+ // Keyboard report
+ byte id = KEYBD_RPT_ID;
+ mHidService.getReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, id, (int) 0);
+ mFutureReportIntent = SettableFuture.create();
+ assertThat(mFutureReportIntent.get()).isEqualTo(KEYBD_RPT_SIZE);
+ assertThat(mReportId).isEqualTo(KEYBD_RPT_ID);
+
+ // Mouse report
+ id = MOUSE_RPT_ID;
+ mHidService.getReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, id, (int) 0);
+ mFutureReportIntent = SettableFuture.create();
+ assertThat(mFutureReportIntent.get()).isEqualTo(MOUSE_RPT_SIZE);
+ assertThat(mReportId).isEqualTo(MOUSE_RPT_ID);
+
+ // Invalid report
+ id = INVALID_RPT_ID;
+ mHidService.getReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, id, (int) 0);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get())
+ .isEqualTo(BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID);
+ }
+
+ /**
+ * Test Set Report
+ *
+ * <ol>
+ * <li>1. Android creates bonding and connect the HID Device
+ * <li>2. Android Set report and verifies the report
+ * </ol>
+ */
+ @Test
+ public void hidSetReportTest() throws Exception {
+ Iterator<ReportEvent> mHidReportEventObserver =
+ mHidBlockingStub
+ .withDeadlineAfter(PROTO_MODE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
+ .onSetReport(Empty.getDefaultInstance());
+ // Keyboard report
+ String kbReportData = "010203040506070809";
+ mHidService.setReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, kbReportData);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get()).isEqualTo(BluetoothHidDevice.ERROR_RSP_SUCCESS);
+ if (mHidReportEventObserver.hasNext()) {
+ ReportEvent hidReportEvent = mHidReportEventObserver.next();
+ assertThat(hidReportEvent.getReportTypeValue())
+ .isEqualTo(BluetoothHidHost.REPORT_TYPE_INPUT);
+ assertThat(hidReportEvent.getReportIdValue()).isEqualTo(KEYBD_RPT_ID);
+ assertThat(hidReportEvent.getReportData()).isEqualTo(kbReportData.substring(2));
+ }
+ // Keyboard report - Invalid param
+ mHidService.setReport(
+ mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, kbReportData.substring(0, 10));
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get())
+ .isEqualTo(BluetoothHidDevice.ERROR_RSP_INVALID_PARAM);
+ if (mHidReportEventObserver.hasNext()) {
+ ReportEvent hidReportEvent = mHidReportEventObserver.next();
+ assertThat(hidReportEvent.getReportTypeValue())
+ .isEqualTo(BluetoothHidHost.REPORT_TYPE_INPUT);
+ assertThat(hidReportEvent.getReportIdValue()).isEqualTo(KEYBD_RPT_ID);
+ assertThat(hidReportEvent.getReportData()).isEqualTo(kbReportData.substring(2, 10));
+ }
+ // Mouse report
+ String mouseReportData = "02030405";
+ mHidService.setReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, mouseReportData);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get()).isEqualTo(BluetoothHidDevice.ERROR_RSP_SUCCESS);
+ if (mHidReportEventObserver.hasNext()) {
+ ReportEvent hidReportEvent = mHidReportEventObserver.next();
+ assertThat(hidReportEvent.getReportTypeValue())
+ .isEqualTo(BluetoothHidHost.REPORT_TYPE_INPUT);
+ assertThat(hidReportEvent.getReportIdValue()).isEqualTo(MOUSE_RPT_ID);
+ assertThat(hidReportEvent.getReportData()).isEqualTo(mouseReportData.substring(2));
+ }
+ // Invalid report id
+ String inValidReportData = "0304";
+ mHidService.setReport(mDevice, BluetoothHidHost.REPORT_TYPE_INPUT, inValidReportData);
+ mFutureHandShakeIntent = SettableFuture.create();
+ assertThat(mFutureHandShakeIntent.get())
+ .isEqualTo(BluetoothHidDevice.ERROR_RSP_INVALID_RPT_ID);
+ if (mHidReportEventObserver.hasNext()) {
+ ReportEvent hidReportEvent = mHidReportEventObserver.next();
+ assertThat(hidReportEvent.getReportTypeValue())
+ .isEqualTo(BluetoothHidHost.REPORT_TYPE_INPUT);
+ assertThat(hidReportEvent.getReportIdValue()).isEqualTo(INVALID_RPT_ID);
+ assertThat(hidReportEvent.getReportData()).isEqualTo(inValidReportData.substring(2));
+ }
}
private void reconnectionFromRemoteAndVerifyDisconnectedState() throws Exception {
@@ -385,9 +622,6 @@ public class HidHostTest {
}
private void bluetoothRestart() throws Exception {
- mContext.registerReceiver(
- mConnectionStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-
mAdapter.disable();
mFutureAdapterStateIntent = SettableFuture.create();
assertThat(mFutureAdapterStateIntent.get()).isEqualTo(BluetoothAdapter.STATE_OFF);
diff --git a/pandora/interfaces/pandora_experimental/hid.proto b/pandora/interfaces/pandora_experimental/hid.proto
index 7b1e7d2c8a..709d1577ff 100644
--- a/pandora/interfaces/pandora_experimental/hid.proto
+++ b/pandora/interfaces/pandora_experimental/hid.proto
@@ -15,8 +15,11 @@ service HID {
rpc VirtualCableUnplugHost(google.protobuf.Empty) returns (google.protobuf.Empty);
// Send a SET_REPORT command, acting as a HID host, to a connected HID device
rpc SendHostReport(SendHostReportRequest) returns (SendHostReportResponse);
+ // receive Protocol Mode Event
+ rpc OnSetProtocolMode(google.protobuf.Empty) returns (stream ProtocolModeEvent);
+ // receive Report Event
+ rpc OnSetReport(google.protobuf.Empty) returns (stream ReportEvent);
}
-
// Enum values match those in BluetoothHidHost.java
enum HidReportType {
HID_REPORT_TYPE_UNSPECIFIED = 0;
@@ -24,6 +27,17 @@ enum HidReportType {
HID_REPORT_TYPE_OUTPUT = 2;
HID_REPORT_TYPE_FEATURE = 3;
}
+// Enum values match those in BluetoothHidHost.java
+enum ProtocolMode {
+ PROTOCOL_REPORT_MODE = 0;
+ PROTOCOL_BOOT_MODE = 1;
+ PROTOCOL_UNSUPPORTED_MODE = 255;
+}
+enum HidReportId {
+ HID_KEYBD_RPT_ID = 0;
+ HID_MOUSE_RPT_ID = 1;
+ HID_INVALID_RPT_ID = 3;
+}
message SendHostReportRequest {
bytes address = 1;
@@ -34,3 +48,13 @@ message SendHostReportRequest {
message SendHostReportResponse {
}
+
+message ProtocolModeEvent {
+ ProtocolMode protocol_mode = 1;
+}
+
+message ReportEvent {
+ HidReportType report_type = 1;
+ HidReportId report_id = 2;
+ string report_data = 3;
+}
diff --git a/pandora/server/bumble_experimental/hid.py b/pandora/server/bumble_experimental/hid.py
index 497f5edd4a..6da25ec6e1 100644
--- a/pandora/server/bumble_experimental/hid.py
+++ b/pandora/server/bumble_experimental/hid.py
@@ -11,6 +11,14 @@ from google.protobuf import empty_pb2 # pytype: disable=pyi-error
from pandora_experimental.hid_grpc_aio import HIDServicer
from bumble.pandora import utils
+from pandora_experimental.hid_pb2 import (
+ ProtocolModeEvent,
+ ReportEvent,
+ PROTOCOL_REPORT_MODE,
+ PROTOCOL_BOOT_MODE,
+ PROTOCOL_UNSUPPORTED_MODE,
+)
+
from bumble.core import (
BT_BR_EDR_TRANSPORT,
BT_L2CAP_PROTOCOL_ID,
@@ -489,20 +497,30 @@ def sdp_records():
# -----------------------------------------------------------------------------
def hogp_device(device):
- global input_report_characteristic
# Create an 'input report' characteristic to send keyboard reports to the host
- input_report_characteristic = Characteristic(
+ input_report_kb_characteristic = Characteristic(
GATT_REPORT_CHARACTERISTIC,
Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY,
Characteristic.READABLE | Characteristic.WRITEABLE,
- bytes([0, 0, 0, 0, 0, 0, 0, 0]),
+ bytes([0, 0, 0, 0, 0, 0, 0, 0, 0]),
[Descriptor(
GATT_REPORT_REFERENCE_DESCRIPTOR,
Descriptor.READABLE,
bytes([0x01, HID_INPUT_REPORT]),
)],
)
-
+ # Create an 'input report' characteristic to send mouse reports to the host
+ input_report_mouse_characteristic = Characteristic(
+ GATT_REPORT_CHARACTERISTIC,
+ Characteristic.Properties.READ | Characteristic.Properties.WRITE | Characteristic.Properties.NOTIFY,
+ Characteristic.READABLE | Characteristic.WRITEABLE,
+ bytes([0, 0, 0, 0]),
+ [Descriptor(
+ GATT_REPORT_REFERENCE_DESCRIPTOR,
+ Descriptor.READABLE,
+ bytes([0x02, HID_INPUT_REPORT]),
+ )],
+ )
# Create an 'output report' characteristic to receive keyboard reports from the host
output_report_characteristic = Characteristic(
GATT_REPORT_CHARACTERISTIC,
@@ -558,7 +576,8 @@ def hogp_device(device):
Characteristic.READABLE,
HID_KEYBOARD_REPORT_MAP,
),
- input_report_characteristic,
+ input_report_kb_characteristic,
+ input_report_mouse_characteristic,
output_report_characteristic,
],
),
@@ -630,6 +649,12 @@ def on_set_report_cb(report_id: int, report_type: int, report_size: int, data: b
logging.info("SET_REPORT report_id: " + str(report_id) + "report_type: " + str(report_type) + "report_size " +
str(report_size) + "data:" + str(data))
+ report = ReportEvent()
+ report.report_type = report_type
+ report.report_id = report_id
+ report.report_data = str(data.hex())
+ hid_report_queue.put_nowait(report)
+
if report_type == Message.ReportType.FEATURE_REPORT:
retValue.status = hid_device.GetSetReturn.ERR_INVALID_PARAMETER
elif report_type == Message.ReportType.INPUT_REPORT:
@@ -657,7 +682,15 @@ def on_get_protocol_cb():
def on_set_protocol_cb(protocol: int):
retValue = hid_device.GetSetStatus()
# We do not support SET_PROTOCOL.
- logging.info(f"SET_PROTOCOL report_id: {protocol}")
+ logging.info(f"SET_PROTOCOL mode: {protocol}")
+ mode = ProtocolModeEvent()
+ if protocol == PROTOCOL_REPORT_MODE:
+ mode.protocol_mode = PROTOCOL_REPORT_MODE
+ elif protocol == PROTOCOL_BOOT_MODE:
+ mode.protocol_mode = PROTOCOL_BOOT_MODE
+ else:
+ mode.protocol_mode = PROTOCOL_UNSUPPORTED_MODE
+ hid_protoMode_queue.put_nowait(mode)
retValue.status = hid_device.GetSetReturn.ERR_UNSUPPORTED_REQUEST
return retValue
@@ -667,6 +700,9 @@ def on_virtual_cable_unplug_cb():
asyncio.create_task(handle_virtual_cable_unplug())
+hid_protoMode_queue = None
+
+
# This class implements the Hid Pandora interface.
class HIDService(HIDServicer):
@@ -676,6 +712,7 @@ class HIDService(HIDServicer):
super().__init__()
self.device = device
self.device.sdp_service_records.update(sdp_records())
+ self.event_queue: Optional[asyncio.Queue[ProtocolModeEvent]] = None
hogp_device(self.device)
logging.info(f'Hid device register: ')
global hid_device
@@ -742,3 +779,43 @@ class HIDService(HIDServicer):
logging.exception(f'Device does not exist')
raise e
return empty_pb2.Empty()
+
+ @utils.rpc
+ async def OnSetProtocolMode(self, request: empty_pb2.Empty,
+ context: grpc.ServicerContext) -> AsyncGenerator[ProtocolModeEvent, None]:
+ logging.info(f'OnSetProtocolMode')
+
+ if self.event_queue is not None:
+ raise RuntimeError('already streaming OnSetProtocolMode events')
+
+ self.event_queue = asyncio.Queue()
+ global hid_protoMode_queue
+ hid_protoMode_queue = self.event_queue
+
+ try:
+ while event := await hid_protoMode_queue.get():
+ yield event
+
+ finally:
+ self.event_queue = None
+ hid_protoMode_queue = None
+
+ @utils.rpc
+ async def OnSetReport(self, request: empty_pb2.Empty,
+ context: grpc.ServicerContext) -> AsyncGenerator[ReportEvent, None]:
+ logging.info(f'OnSetReport')
+
+ if self.event_queue is not None:
+ raise RuntimeError('already streaming OnSetReport events')
+
+ self.event_queue = asyncio.Queue()
+ global hid_report_queue
+ hid_report_queue = self.event_queue
+
+ try:
+ while event := await hid_report_queue.get():
+ yield event
+
+ finally:
+ self.event_queue = None
+ hid_report_queue = None