Loading framework/tests/bumble/src/android/bluetooth/HidHostTest.java 0 → 100644 +211 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.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 com.google.protobuf.Empty; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import pandora.HIDGrpc; /** Test cases for {@link Hid Host}. */ @RunWith(AndroidJUnit4.class) public class HidHostTest { private static final String TAG = "HidHostTest"; private SettableFuture<Integer> mFutureConnectionIntent, mFutureBondIntent; private SettableFuture<Boolean> mAclConnectionIntent; private BluetoothDevice mDevice; private BluetoothHidHost mService; private final Context mContext = ApplicationProvider.getApplicationContext(); private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class); private final BluetoothAdapter mAdapter = mManager.getAdapter(); private HIDGrpc.HIDBlockingStub mHidBlockingStub; @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 mConnectionStateReceiver = 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); } } } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { mBumble.getRemoteDevice().setPairingConfirmation(true); } 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); } } } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) { if (mAclConnectionIntent != null) { mAclConnectionIntent.set(true); } } } }; // These callbacks run on the main thread. private final class HidHostServiceListener implements BluetoothProfile.ServiceListener { public void onServiceConnected(int profile, BluetoothProfile proxy) { mService = (BluetoothHidHost) proxy; } public void onServiceDisconnected(int profile) {} } @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)); mAdapter.getProfileProxy(mContext, new HidHostServiceListener(), BluetoothProfile.HID_HOST); mHidBlockingStub = mBumble.hidBlocking(); mFutureConnectionIntent = SettableFuture.create(); mDevice = mBumble.getRemoteDevice(); assertThat(mDevice.createBond()).isTrue(); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_CONNECTED); } @After public void tearDown() throws Exception { if (mDevice.getBondState() == BluetoothDevice.BOND_BONDED) { mFutureBondIntent = SettableFuture.create(); mDevice.removeBond(); assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE); } if (mDevice.isConnected()) { mAclConnectionIntent = SettableFuture.create(); mDevice.disconnect(); assertThat(mAclConnectionIntent.get()).isTrue(); } mContext.unregisterReceiver(mConnectionStateReceiver); } /** * Test HID Disconnection: * * <ol> * <li>1. Android tries to create bond, emitting bonding intent 4. Android confirms the * pairing via pairing request intent * <li>2. Bumble confirms the pairing internally * <li>3. Android tries to HID connect and verifies Connection state intent * <li>4. Bumble Disconnect the HID and Android verifies Connection state intent * </ol> */ @Test public void disconnectHidDeviceTest() throws Exception { mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.disconnectHost(Empty.getDefaultInstance()); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } /** * Test HID Connection Policy Disable: * * <ol> * <li>1. Android tries to create bond, emitting bonding intent 4. Android confirms the * pairing via pairing request intent * <li>2. Bumble confirms the pairing internally * <li>3. Android tries to HID connect and verifies Connection state intent * <li>4. Bumble Disconnect the HID and Android verifies Connection state intent * <li>5. Android Disable the HID connection policy * <li>6. Bumble connetct the HID and Android verifies Connection state intent * </ol> */ @Test @RequiresFlagsEnabled({ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP, Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY }) public void disableHidConnectionPolicyTest() throws Exception { mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.disconnectHost(Empty.getDefaultInstance()); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); assertThat( mService.setConnectionPolicy( mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)) .isTrue(); mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.connectHost(Empty.getDefaultInstance()); assertThat(mService.getConnectionState(mDevice)) .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } } framework/tests/bumble/src/android/bluetooth/PandoraDevice.java +11 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import org.junit.rules.ExternalResource; import pandora.DckGrpc; import pandora.GATTGrpc; import pandora.HIDGrpc; import pandora.HostGrpc; import pandora.HostProto; import pandora.RFCOMMGrpc; Loading Loading @@ -118,6 +119,16 @@ public final class PandoraDevice extends ExternalResource { return HostGrpc.newBlockingStub(mChannel); } /** Get Pandora HID service */ public HIDGrpc.HIDStub hid() { return HIDGrpc.newStub(mChannel); } /** Get Pandora HID blocking service */ public HIDGrpc.HIDBlockingStub hidBlocking() { return HIDGrpc.newBlockingStub(mChannel); } /** Get Pandora Dck service */ public DckGrpc.DckStub dck() { return DckGrpc.newStub(mChannel); Loading framework/tests/bumble/src/bumble_server.py +4 −0 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ from bumble_experimental.dck import DckService from bumble_experimental.gatt import GATTService from bumble_experimental.rfcomm import RFCOMMService from bumble_experimental.avrcp import AvrcpService from bumble_experimental.hid import HIDService from pandora_experimental.asha_grpc_aio import add_AshaServicer_to_server from pandora_experimental.dck_grpc_aio import add_DckServicer_to_server from pandora_experimental.gatt_grpc_aio import add_GATTServicer_to_server from pandora_experimental.rfcomm_grpc_aio import add_RFCOMMServicer_to_server from pandora_experimental.avrcp_grpc_aio import add_AVRCPServicer_to_server from pandora_experimental.hid_grpc_aio import add_HIDServicer_to_server from typing import Any, Dict Loading Loading @@ -81,6 +83,8 @@ def register_experimental_services() -> None: lambda bumble, _, server: add_GATTServicer_to_server(GATTService(bumble.device), server)) bumble_server.register_servicer_hook( lambda bumble, _, server: add_RFCOMMServicer_to_server(RFCOMMService(bumble.device), server)) bumble_server.register_servicer_hook( lambda bumble, _, server: add_HIDServicer_to_server(HIDService(bumble.device), server)) def retrieve_config(config: str) -> Dict[str, Any]: Loading pandora/interfaces/pandora_experimental/hid.proto +7 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,13 @@ package pandora; option java_outer_classname = "HidProto"; import "google/protobuf/empty.proto"; service HID { // Connect HID Host rpc ConnectHost(google.protobuf.Empty) returns (google.protobuf.Empty); // Disconnect HID Host rpc DisconnectHost(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); } Loading pandora/server/bumble_experimental/hid.py 0 → 100644 +395 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
framework/tests/bumble/src/android/bluetooth/HidHostTest.java 0 → 100644 +211 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.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 com.google.protobuf.Empty; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import pandora.HIDGrpc; /** Test cases for {@link Hid Host}. */ @RunWith(AndroidJUnit4.class) public class HidHostTest { private static final String TAG = "HidHostTest"; private SettableFuture<Integer> mFutureConnectionIntent, mFutureBondIntent; private SettableFuture<Boolean> mAclConnectionIntent; private BluetoothDevice mDevice; private BluetoothHidHost mService; private final Context mContext = ApplicationProvider.getApplicationContext(); private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class); private final BluetoothAdapter mAdapter = mManager.getAdapter(); private HIDGrpc.HIDBlockingStub mHidBlockingStub; @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 mConnectionStateReceiver = 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); } } } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { mBumble.getRemoteDevice().setPairingConfirmation(true); } 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); } } } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(intent.getAction())) { if (mAclConnectionIntent != null) { mAclConnectionIntent.set(true); } } } }; // These callbacks run on the main thread. private final class HidHostServiceListener implements BluetoothProfile.ServiceListener { public void onServiceConnected(int profile, BluetoothProfile proxy) { mService = (BluetoothHidHost) proxy; } public void onServiceDisconnected(int profile) {} } @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)); mAdapter.getProfileProxy(mContext, new HidHostServiceListener(), BluetoothProfile.HID_HOST); mHidBlockingStub = mBumble.hidBlocking(); mFutureConnectionIntent = SettableFuture.create(); mDevice = mBumble.getRemoteDevice(); assertThat(mDevice.createBond()).isTrue(); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_CONNECTED); } @After public void tearDown() throws Exception { if (mDevice.getBondState() == BluetoothDevice.BOND_BONDED) { mFutureBondIntent = SettableFuture.create(); mDevice.removeBond(); assertThat(mFutureBondIntent.get()).isEqualTo(BluetoothDevice.BOND_NONE); } if (mDevice.isConnected()) { mAclConnectionIntent = SettableFuture.create(); mDevice.disconnect(); assertThat(mAclConnectionIntent.get()).isTrue(); } mContext.unregisterReceiver(mConnectionStateReceiver); } /** * Test HID Disconnection: * * <ol> * <li>1. Android tries to create bond, emitting bonding intent 4. Android confirms the * pairing via pairing request intent * <li>2. Bumble confirms the pairing internally * <li>3. Android tries to HID connect and verifies Connection state intent * <li>4. Bumble Disconnect the HID and Android verifies Connection state intent * </ol> */ @Test public void disconnectHidDeviceTest() throws Exception { mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.disconnectHost(Empty.getDefaultInstance()); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } /** * Test HID Connection Policy Disable: * * <ol> * <li>1. Android tries to create bond, emitting bonding intent 4. Android confirms the * pairing via pairing request intent * <li>2. Bumble confirms the pairing internally * <li>3. Android tries to HID connect and verifies Connection state intent * <li>4. Bumble Disconnect the HID and Android verifies Connection state intent * <li>5. Android Disable the HID connection policy * <li>6. Bumble connetct the HID and Android verifies Connection state intent * </ol> */ @Test @RequiresFlagsEnabled({ Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP, Flags.FLAG_SAVE_INITIAL_HID_CONNECTION_POLICY }) public void disableHidConnectionPolicyTest() throws Exception { mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.disconnectHost(Empty.getDefaultInstance()); assertThat(mFutureConnectionIntent.get()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED); assertThat( mService.setConnectionPolicy( mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)) .isTrue(); mFutureConnectionIntent = SettableFuture.create(); mHidBlockingStub.connectHost(Empty.getDefaultInstance()); assertThat(mService.getConnectionState(mDevice)) .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } }
framework/tests/bumble/src/android/bluetooth/PandoraDevice.java +11 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import org.junit.rules.ExternalResource; import pandora.DckGrpc; import pandora.GATTGrpc; import pandora.HIDGrpc; import pandora.HostGrpc; import pandora.HostProto; import pandora.RFCOMMGrpc; Loading Loading @@ -118,6 +119,16 @@ public final class PandoraDevice extends ExternalResource { return HostGrpc.newBlockingStub(mChannel); } /** Get Pandora HID service */ public HIDGrpc.HIDStub hid() { return HIDGrpc.newStub(mChannel); } /** Get Pandora HID blocking service */ public HIDGrpc.HIDBlockingStub hidBlocking() { return HIDGrpc.newBlockingStub(mChannel); } /** Get Pandora Dck service */ public DckGrpc.DckStub dck() { return DckGrpc.newStub(mChannel); Loading
framework/tests/bumble/src/bumble_server.py +4 −0 Original line number Diff line number Diff line Loading @@ -25,12 +25,14 @@ from bumble_experimental.dck import DckService from bumble_experimental.gatt import GATTService from bumble_experimental.rfcomm import RFCOMMService from bumble_experimental.avrcp import AvrcpService from bumble_experimental.hid import HIDService from pandora_experimental.asha_grpc_aio import add_AshaServicer_to_server from pandora_experimental.dck_grpc_aio import add_DckServicer_to_server from pandora_experimental.gatt_grpc_aio import add_GATTServicer_to_server from pandora_experimental.rfcomm_grpc_aio import add_RFCOMMServicer_to_server from pandora_experimental.avrcp_grpc_aio import add_AVRCPServicer_to_server from pandora_experimental.hid_grpc_aio import add_HIDServicer_to_server from typing import Any, Dict Loading Loading @@ -81,6 +83,8 @@ def register_experimental_services() -> None: lambda bumble, _, server: add_GATTServicer_to_server(GATTService(bumble.device), server)) bumble_server.register_servicer_hook( lambda bumble, _, server: add_RFCOMMServicer_to_server(RFCOMMService(bumble.device), server)) bumble_server.register_servicer_hook( lambda bumble, _, server: add_HIDServicer_to_server(HIDService(bumble.device), server)) def retrieve_config(config: str) -> Dict[str, Any]: Loading
pandora/interfaces/pandora_experimental/hid.proto +7 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,13 @@ package pandora; option java_outer_classname = "HidProto"; import "google/protobuf/empty.proto"; service HID { // Connect HID Host rpc ConnectHost(google.protobuf.Empty) returns (google.protobuf.Empty); // Disconnect HID Host rpc DisconnectHost(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); } Loading
pandora/server/bumble_experimental/hid.py 0 → 100644 +395 −0 File added.Preview size limit exceeded, changes collapsed. Show changes