bluetooth: Update connected state with ACL connection states
This CL also renames PopulatePairedDevice() to PopulateOrUpdatePairedDevice() because the connected event comes in earlier than the paired event, and we don't want to lose the connected status. That means we may need to issue OnDeviceChanged() instead of OnDeviceAdded() when we receive paired events. Bug: 408266490 Test: Web pages get connect/disconnect events. Test: Connection state is connected after paired. Change-Id: I87f649e66c6a64d0a0472190eec0bbf8ce5f7fe1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6441729 Reviewed-by: Jack Hsieh <chengweih@chromium.org> Commit-Queue: Garfield Tan <xutan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1444885}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
622207aca3
commit
61430495dd
device/bluetooth
@ -707,6 +707,7 @@ if (is_android) {
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/BluetoothLeScannerWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/BluetoothSocketWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/DeviceBondStateReceiverWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/DeviceConnectStateReceiverWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/ScanCallbackWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/ScanResultWrapper.java",
|
||||
"android/java/src/org/chromium/device/bluetooth/wrapper/ScanResultWrapperImpl.java",
|
||||
|
@ -31,6 +31,7 @@ import org.chromium.components.location.LocationUtils;
|
||||
import org.chromium.device.bluetooth.wrapper.BluetoothAdapterWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.BluetoothDeviceWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.DeviceBondStateReceiverWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.DeviceConnectStateReceiverWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.ScanResultWrapper;
|
||||
|
||||
import java.util.List;
|
||||
@ -54,6 +55,7 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
private final @Nullable ChromeBluetoothLeScanner mLeScanner;
|
||||
|
||||
private @Nullable DeviceBondStateReceiverWrapper mDeviceBondStateReceiver;
|
||||
private @Nullable DeviceConnectStateReceiverWrapper mDeviceConnectStateReceiver;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Construction and handler for C++ object destruction.
|
||||
@ -94,6 +96,9 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
if (mDeviceBondStateReceiver != null && mAdapter != null) {
|
||||
mAdapter.getContext().unregisterReceiver(mDeviceBondStateReceiver);
|
||||
}
|
||||
if (mDeviceConnectStateReceiver != null && mAdapter != null) {
|
||||
mAdapter.getContext().unregisterReceiver(mDeviceConnectStateReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@ -234,18 +239,29 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
}
|
||||
|
||||
for (BluetoothDeviceWrapper device : devices) {
|
||||
populatePairedDevice(device, /* fromBroadcastReceiver= */ false);
|
||||
populateOrUpdatePairedDevice(device, /* fromBroadcastReceiver= */ false);
|
||||
}
|
||||
|
||||
if (mDeviceBondStateReceiver != null) {
|
||||
return;
|
||||
if (mDeviceBondStateReceiver == null) {
|
||||
mDeviceBondStateReceiver =
|
||||
mAdapter.createDeviceBondStateReceiver(new BondedStateReceiver());
|
||||
ContextUtils.registerProtectedBroadcastReceiver(
|
||||
mAdapter.getContext(),
|
||||
mDeviceBondStateReceiver,
|
||||
new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
|
||||
}
|
||||
|
||||
// Register connect state listener here because it requires the BLUETOOTH_CONNECT permission
|
||||
// and this is used to provide correct connect states when Web Serial API is used.
|
||||
if (mDeviceConnectStateReceiver == null) {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
mDeviceConnectStateReceiver =
|
||||
mAdapter.createDeviceConnectStateReceiver(new ConnectStateReceiver());
|
||||
ContextUtils.registerProtectedBroadcastReceiver(
|
||||
mAdapter.getContext(), mDeviceConnectStateReceiver, intentFilter);
|
||||
}
|
||||
mDeviceBondStateReceiver =
|
||||
mAdapter.createDeviceBondStateReceiver(new BondedStateReceiver());
|
||||
ContextUtils.registerProtectedBroadcastReceiver(
|
||||
mAdapter.getContext(),
|
||||
mDeviceBondStateReceiver,
|
||||
new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@ -295,10 +311,10 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePairedDevice(
|
||||
private void populateOrUpdatePairedDevice(
|
||||
BluetoothDeviceWrapper deviceWrapper, boolean fromBroadcastReceiver) {
|
||||
ChromeBluetoothAdapterJni.get()
|
||||
.populatePairedDevice(
|
||||
.populateOrUpdatePairedDevice(
|
||||
mNativeBluetoothAdapterAndroid,
|
||||
this,
|
||||
deviceWrapper.getAddress(),
|
||||
@ -397,11 +413,11 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
public class BondedStateReceiver implements DeviceBondStateReceiverWrapper.Callback {
|
||||
private class BondedStateReceiver implements DeviceBondStateReceiverWrapper.Callback {
|
||||
@Override
|
||||
public void onDeviceBondStateChanged(BluetoothDeviceWrapper device, int bondState) {
|
||||
if (bondState == BluetoothDevice.BOND_BONDED) {
|
||||
populatePairedDevice(device, /* fromBroadcastReceiver= */ true);
|
||||
populateOrUpdatePairedDevice(device, /* fromBroadcastReceiver= */ true);
|
||||
}
|
||||
if (bondState == BluetoothDevice.BOND_NONE) {
|
||||
ChromeBluetoothAdapterJni.get()
|
||||
@ -413,6 +429,26 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectStateReceiver implements DeviceConnectStateReceiverWrapper.Callback {
|
||||
@Override
|
||||
public void onDeviceConnectStateChanged(
|
||||
BluetoothDeviceWrapper device, int transport, boolean connected) {
|
||||
if (transport == BluetoothDevice.TRANSPORT_AUTO) {
|
||||
// EXTRA_TRANSPORT was added in API level 33 (Android 13/T), so just assign a value
|
||||
// when it's absent.
|
||||
transport = BluetoothDevice.TRANSPORT_BREDR;
|
||||
}
|
||||
ChromeBluetoothAdapterJni.get()
|
||||
.updateDeviceAclConnectState(
|
||||
mNativeBluetoothAdapterAndroid,
|
||||
ChromeBluetoothAdapter.this,
|
||||
device.getAddress(),
|
||||
device,
|
||||
transport,
|
||||
connected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@ -483,8 +519,8 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
Object[] manufacturerDataValues,
|
||||
int advertiseFlags);
|
||||
|
||||
// Binds to BluetoothAdapterAndroid::PopulatePairedDevice.
|
||||
void populatePairedDevice(
|
||||
// Binds to BluetoothAdapterAndroid::PopulateOrUpdatePairedDevice.
|
||||
void populateOrUpdatePairedDevice(
|
||||
long nativeBluetoothAdapterAndroid,
|
||||
ChromeBluetoothAdapter caller,
|
||||
String address,
|
||||
@ -495,6 +531,15 @@ final class ChromeBluetoothAdapter extends BroadcastReceiver {
|
||||
void onDeviceUnpaired(
|
||||
long nativeBluetoothAdapterAndroid, ChromeBluetoothAdapter caller, String address);
|
||||
|
||||
// Binds to BluetoothAdapterAndroid::UpdateDeviceAclConnectState
|
||||
void updateDeviceAclConnectState(
|
||||
long nativeBluetoothAdapterAndroid,
|
||||
ChromeBluetoothAdapter caller,
|
||||
String address,
|
||||
BluetoothDeviceWrapper deviceWrapper,
|
||||
int transport,
|
||||
boolean connected);
|
||||
|
||||
// Binds to BluetoothAdapterAndroid::nativeOnAdapterStateChanged
|
||||
void onAdapterStateChanged(
|
||||
long nativeBluetoothAdapterAndroid, ChromeBluetoothAdapter caller, boolean powered);
|
||||
|
5
device/bluetooth/android/java/src/org/chromium/device/bluetooth/wrapper/BluetoothAdapterWrapper.java
5
device/bluetooth/android/java/src/org/chromium/device/bluetooth/wrapper/BluetoothAdapterWrapper.java
@ -178,4 +178,9 @@ public class BluetoothAdapterWrapper {
|
||||
DeviceBondStateReceiverWrapper.Callback callback) {
|
||||
return new DeviceBondStateReceiverWrapper(callback);
|
||||
}
|
||||
|
||||
public DeviceConnectStateReceiverWrapper createDeviceConnectStateReceiver(
|
||||
DeviceConnectStateReceiverWrapper.Callback callback) {
|
||||
return new DeviceConnectStateReceiverWrapper(callback);
|
||||
}
|
||||
}
|
||||
|
46
device/bluetooth/android/java/src/org/chromium/device/bluetooth/wrapper/DeviceConnectStateReceiverWrapper.java
Normal file
46
device/bluetooth/android/java/src/org/chromium/device/bluetooth/wrapper/DeviceConnectStateReceiverWrapper.java
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.device.bluetooth.wrapper;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.chromium.build.annotations.NullMarked;
|
||||
|
||||
@NullMarked
|
||||
public class DeviceConnectStateReceiverWrapper extends BroadcastReceiver {
|
||||
private Callback mCallback;
|
||||
|
||||
DeviceConnectStateReceiverWrapper(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
boolean isConnected;
|
||||
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
|
||||
isConnected = true;
|
||||
} else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
|
||||
isConnected = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
int transport =
|
||||
intent.getIntExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_AUTO);
|
||||
|
||||
mCallback.onDeviceConnectStateChanged(
|
||||
new BluetoothDeviceWrapper(device), transport, isConnected);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onDeviceConnectStateChanged(
|
||||
BluetoothDeviceWrapper device, int transport, boolean connected);
|
||||
}
|
||||
}
|
@ -216,15 +216,12 @@ void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan(
|
||||
auto iter = devices_.find(device_address);
|
||||
|
||||
bool is_new_device = false;
|
||||
std::unique_ptr<BluetoothDeviceAndroid> device_android_owner;
|
||||
BluetoothDeviceAndroid* device_android;
|
||||
|
||||
if (iter == devices_.end()) {
|
||||
// New device.
|
||||
is_new_device = true;
|
||||
device_android_owner = BluetoothDeviceAndroid::Create(
|
||||
this, bluetooth_device_wrapper, ui_task_runner_, socket_thread_);
|
||||
device_android = device_android_owner.get();
|
||||
device_android = CreateDevice(device_address, bluetooth_device_wrapper);
|
||||
} else {
|
||||
// Existing device.
|
||||
device_android = static_cast<BluetoothDeviceAndroid*>(iter->second.get());
|
||||
@ -297,7 +294,6 @@ void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan(
|
||||
}
|
||||
|
||||
if (is_new_device) {
|
||||
devices_[device_address] = std::move(device_android_owner);
|
||||
for (auto& observer : observers_)
|
||||
observer.DeviceAdded(this, device_android);
|
||||
} else {
|
||||
@ -306,7 +302,7 @@ void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan(
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothAdapterAndroid::PopulatePairedDevice(
|
||||
void BluetoothAdapterAndroid::PopulateOrUpdatePairedDevice(
|
||||
JNIEnv* env,
|
||||
const base::android::JavaParamRef<jobject>& caller,
|
||||
const base::android::JavaParamRef<jstring>& address,
|
||||
@ -318,14 +314,18 @@ void BluetoothAdapterAndroid::PopulatePairedDevice(
|
||||
|
||||
bool is_new_device = iter == devices_.end();
|
||||
if (!is_new_device) {
|
||||
// If an event doesn't come from the broadcast receiver, then we're
|
||||
// pushing already paired devices in GetDevices() from Java code to native
|
||||
// code. There is no need to notify observers because the device paired
|
||||
// state doesn't change.
|
||||
if (from_broadcast_receiver) {
|
||||
NotifyDeviceChanged(iter->second.get());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<BluetoothDeviceAndroid> device_owner =
|
||||
BluetoothDeviceAndroid::Create(this, bluetooth_device_wrapper,
|
||||
ui_task_runner_, socket_thread_);
|
||||
BluetoothDeviceAndroid* device = device_owner.get();
|
||||
devices_[device_address] = std::move(device_owner);
|
||||
BluetoothDeviceAndroid* device =
|
||||
CreateDevice(device_address, bluetooth_device_wrapper);
|
||||
|
||||
// We don't notify observers for populated paired devices unless it's from
|
||||
// bonded state broadcast receiver. See crbug.com/387371131 for more details.
|
||||
@ -364,6 +364,59 @@ void BluetoothAdapterAndroid::OnDeviceUnpaired(
|
||||
duration_before_expiry);
|
||||
}
|
||||
|
||||
void BluetoothAdapterAndroid::UpdateDeviceAclConnectState(
|
||||
JNIEnv* env,
|
||||
const base::android::JavaParamRef<jobject>& caller,
|
||||
const base::android::JavaParamRef<jstring>& address,
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_device_wrapper, // Java Type: BluetoothDeviceWrapper
|
||||
uint8_t transport,
|
||||
bool connected) {
|
||||
std::string device_address = ConvertJavaStringToUTF8(env, address);
|
||||
|
||||
auto iter = devices_.find(device_address);
|
||||
bool is_new_device = iter == devices_.end();
|
||||
if (is_new_device && !connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothDeviceAndroid* device;
|
||||
if (is_new_device) {
|
||||
device = CreateDevice(device_address, bluetooth_device_wrapper);
|
||||
} else {
|
||||
device = static_cast<BluetoothDeviceAndroid*>(iter->second.get());
|
||||
}
|
||||
|
||||
bool was_connected = device->IsConnected();
|
||||
device->UpdateAclConnectState(transport, connected);
|
||||
|
||||
if (is_new_device) {
|
||||
for (auto& observer : observers_) {
|
||||
observer.DeviceAdded(this, device);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a new device.
|
||||
bool is_connected = device->IsConnected();
|
||||
if (was_connected != is_connected) {
|
||||
NotifyDeviceChanged(device);
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothDeviceAndroid* BluetoothAdapterAndroid::CreateDevice(
|
||||
const std::string& device_address,
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_device_wrapper) { // Java Type: BluetoothDeviceWrapper
|
||||
BluetoothDeviceAndroid* device;
|
||||
std::unique_ptr<BluetoothDeviceAndroid> device_owner =
|
||||
BluetoothDeviceAndroid::Create(this, bluetooth_device_wrapper,
|
||||
ui_task_runner_, socket_thread_);
|
||||
device = device_owner.get();
|
||||
devices_[device_address] = std::move(device_owner);
|
||||
return device;
|
||||
}
|
||||
|
||||
BluetoothAdapterAndroid::BluetoothAdapterAndroid() {}
|
||||
|
||||
BluetoothAdapterAndroid::~BluetoothAdapterAndroid() {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_ANDROID_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/android/jni_android.h"
|
||||
#include "base/android/scoped_java_ref.h"
|
||||
@ -13,12 +14,14 @@
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "device/bluetooth/bluetooth_adapter.h"
|
||||
#include "device/bluetooth/bluetooth_device.h"
|
||||
|
||||
using base::android::ScopedJavaLocalRef;
|
||||
|
||||
namespace device {
|
||||
|
||||
class BluetoothSocketThread;
|
||||
class BluetoothDeviceAndroid;
|
||||
|
||||
// BluetoothAdapterAndroid, along with the Java class
|
||||
// org.chromium.device.bluetooth.BluetoothAdapter, implement BluetoothAdapter.
|
||||
@ -118,13 +121,14 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterAndroid final
|
||||
manufacturer_data_values, // Java Type: byte[]
|
||||
int32_t advertisement_flags);
|
||||
|
||||
// Creates a device and adds to the device list if it isn't present.
|
||||
void PopulatePairedDevice(
|
||||
// Called when a new paired device is found or an existing device becomes
|
||||
// paired. It creates a device if it isn't in |devices_|
|
||||
void PopulateOrUpdatePairedDevice(
|
||||
JNIEnv* env,
|
||||
const base::android::JavaParamRef<jobject>& caller,
|
||||
const base::android::JavaParamRef<jstring>& address,
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_device_wrapper, // Java Type: bluetoothDeviceWrapper
|
||||
bluetooth_device_wrapper, // Java Type: BluetoothDeviceWrapper
|
||||
bool from_broadcast_receiver);
|
||||
|
||||
// Called when the Android system notifies us that a device is unpaired.
|
||||
@ -132,6 +136,18 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterAndroid final
|
||||
const base::android::JavaParamRef<jobject>& caller,
|
||||
const base::android::JavaParamRef<jstring>& address);
|
||||
|
||||
// Updates the connected state of the device with |address| if it's in the
|
||||
// device list for |transport| to |connected|. It creates a device if it's
|
||||
// not in |devices_| and connected.
|
||||
void UpdateDeviceAclConnectState(
|
||||
JNIEnv* env,
|
||||
const base::android::JavaParamRef<jobject>& caller,
|
||||
const base::android::JavaParamRef<jstring>& address,
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_device_wrapper, // Java Type: BluetoothDeviceWrapper
|
||||
uint8_t transport,
|
||||
bool connected);
|
||||
|
||||
protected:
|
||||
BluetoothAdapterAndroid();
|
||||
~BluetoothAdapterAndroid() override;
|
||||
@ -159,6 +175,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterAndroid final
|
||||
|
||||
private:
|
||||
void PopulatePairedDevices() const;
|
||||
BluetoothDeviceAndroid* CreateDevice(
|
||||
const std::string& device_address,
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_device_wrapper); // Java Type: BluetoothDeviceWrapper
|
||||
|
||||
scoped_refptr<BluetoothSocketThread> socket_thread_;
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "device/base/features.h"
|
||||
#include "device/bluetooth/android/wrappers.h"
|
||||
#include "device/bluetooth/bluetooth_common.h"
|
||||
#include "device/bluetooth/bluetooth_device.h"
|
||||
#include "device/bluetooth/bluetooth_discovery_filter.h"
|
||||
#include "device/bluetooth/test/bluetooth_scanner_callback.h"
|
||||
#include "device/bluetooth/test/bluetooth_test_android.h"
|
||||
@ -382,15 +383,31 @@ TEST_F(BluetoothAdapterAndroidTest, NotifyObserversForNewPairedDevices) {
|
||||
|
||||
adapter_->GetDevices();
|
||||
|
||||
SimulatePairedClassicDevice(1, true);
|
||||
SimulatePairedClassicDevice(1, /*notify_callback=*/true);
|
||||
ASSERT_EQ(observer.device_added_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
|
||||
SimulatePairedClassicDevice(2, true);
|
||||
SimulatePairedClassicDevice(2, /*notify_callback=*/true);
|
||||
ASSERT_EQ(observer.device_added_count(), 2);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress2);
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, IsConnectedAfterNewDevicePaired) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
|
||||
adapter_->GetDevices();
|
||||
|
||||
SimulatePairedClassicDevice(1, /*notify_callback=*/true);
|
||||
EXPECT_EQ(observer.device_added_count(), 1);
|
||||
EXPECT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_TRUE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, ExposeUuidFromPairedDevices) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
@ -480,6 +497,138 @@ TEST_F(BluetoothAdapterAndroidTest, UnknownDeviceUnpaired) {
|
||||
UnpairDevice(kTestDeviceAddress1);
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclConnected) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
SimulatePairedClassicDevice(1);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
EXPECT_FALSE(device->IsConnected());
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_CLASSIC,
|
||||
/*connected=*/true);
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_TRUE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclDisconnected) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
adapter_->GetDevices();
|
||||
|
||||
SimulatePairedClassicDevice(1, /*notify_callback=*/true);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_CLASSIC,
|
||||
/*connected=*/false);
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_FALSE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclConnectedWithDualTransport) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
SimulatePairedClassicDevice(1);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_CLASSIC,
|
||||
/*connected=*/true);
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_LE,
|
||||
/*connected=*/true);
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_TRUE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclDisconnectedWithDualTransport) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
SimulatePairedClassicDevice(1);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_CLASSIC,
|
||||
/*connected=*/true);
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_LE,
|
||||
/*connected=*/true);
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_CLASSIC,
|
||||
/*connected=*/false);
|
||||
EXPECT_EQ(observer.device_changed_count(), 0);
|
||||
EXPECT_TRUE(device->IsConnected());
|
||||
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_LE,
|
||||
/*connected=*/false);
|
||||
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_FALSE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
// The transport extra of ACL connected/disconnected broadcasts was added in API
|
||||
// level 33 (Android 13/T). On devices where the extra was not provided, we
|
||||
// use BluetoothDevice#TRANSPORT_AUTO (0) as the default value, and later
|
||||
// assign an arbitrary non-zero value (BR/EDR) so that the bit-wise flag takes
|
||||
// effect. Write unit tests to ensure it works fine.
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclConnectedWithoutTransport) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
SimulatePairedClassicDevice(1);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_INVALID,
|
||||
/*connected=*/true);
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_TRUE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, AclDisconnectedWithoutTransport) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
||||
InitWithFakeAdapter();
|
||||
|
||||
adapter_->GetDevices();
|
||||
|
||||
SimulatePairedClassicDevice(1, /*notify_callback=*/true);
|
||||
|
||||
BluetoothDevice* device = adapter_->GetDevice(kTestDeviceAddress1);
|
||||
|
||||
TestBluetoothAdapterObserver observer(adapter_.get());
|
||||
SimulateAclConnectStateChange(device, BLUETOOTH_TRANSPORT_INVALID,
|
||||
/*connected=*/false);
|
||||
ASSERT_EQ(observer.device_changed_count(), 1);
|
||||
EXPECT_EQ(observer.last_device()->GetAddress(), kTestDeviceAddress1);
|
||||
EXPECT_FALSE(observer.last_device()->IsConnected());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothAdapterAndroidTest, ScanFailsWithoutLeSupport) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(features::kBluetoothRfcommAndroid);
|
||||
|
@ -125,7 +125,7 @@ bool BluetoothDeviceAndroid::IsPaired() const {
|
||||
}
|
||||
|
||||
bool BluetoothDeviceAndroid::IsConnected() const {
|
||||
return IsGattConnected();
|
||||
return IsGattConnected() || connected_transport_;
|
||||
}
|
||||
|
||||
bool BluetoothDeviceAndroid::IsGattConnected() const {
|
||||
@ -342,4 +342,13 @@ void BluetoothDeviceAndroid::DisconnectGatt() {
|
||||
Java_ChromeBluetoothDevice_disconnectGatt(AttachCurrentThread(), j_device_);
|
||||
}
|
||||
|
||||
void BluetoothDeviceAndroid::UpdateAclConnectState(uint8_t transport,
|
||||
bool connected) {
|
||||
if (connected) {
|
||||
connected_transport_ |= transport;
|
||||
} else {
|
||||
connected_transport_ &= ~transport;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace device
|
||||
|
@ -120,6 +120,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceAndroid final
|
||||
const base::android::JavaParamRef<jobject>&
|
||||
bluetooth_gatt_service_wrapper); // BluetoothGattServiceWrapper
|
||||
|
||||
// Update the connected state of |transport| to |connected|.
|
||||
void UpdateAclConnectState(uint8_t transport, bool connected);
|
||||
|
||||
private:
|
||||
BluetoothDeviceAndroid(
|
||||
BluetoothAdapterAndroid* adapter,
|
||||
@ -138,6 +141,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceAndroid final
|
||||
scoped_refptr<BluetoothSocketThread> socket_thread_;
|
||||
|
||||
bool gatt_connected_ = false;
|
||||
|
||||
// A bit-wise flag indicating connected states of Bluetooth transports.
|
||||
uint8_t connected_transport_ = 0;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
@ -39,6 +39,7 @@ import org.chromium.device.bluetooth.wrapper.BluetoothGattWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.BluetoothLeScannerWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.BluetoothSocketWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.DeviceBondStateReceiverWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.DeviceConnectStateReceiverWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.ScanCallbackWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.ScanResultWrapper;
|
||||
import org.chromium.device.bluetooth.wrapper.ThreadUtilsWrapper;
|
||||
@ -139,8 +140,9 @@ class Fakes {
|
||||
private final FakeBluetoothLeScanner mFakeScanner;
|
||||
private boolean mPowered = true;
|
||||
private int mEnabledDeviceTransport = BluetoothDevice.DEVICE_TYPE_DUAL;
|
||||
final ArraySet<BluetoothDeviceWrapper> mFakePairedDevices = new ArraySet<>();
|
||||
private final ArraySet<BluetoothDeviceWrapper> mFakePairedDevices = new ArraySet();
|
||||
private DeviceBondStateReceiverWrapper.Callback mDeviceBondStateCallback;
|
||||
DeviceConnectStateReceiverWrapper.Callback mDeviceConnectStateCallback;
|
||||
final long mNativeBluetoothTestAndroid;
|
||||
|
||||
/** Creates a FakeBluetoothAdapter. */
|
||||
@ -404,6 +406,12 @@ class Fakes {
|
||||
}
|
||||
|
||||
mFakePairedDevices.add(device);
|
||||
// When a device becomes paired, it needs to be connected first. The connection state
|
||||
// broadcast comes before the bond broadcast.
|
||||
if (notifyCallback && mDeviceConnectStateCallback != null) {
|
||||
mDeviceConnectStateCallback.onDeviceConnectStateChanged(
|
||||
device, BluetoothDevice.TRANSPORT_BREDR, true);
|
||||
}
|
||||
if (notifyCallback && mDeviceBondStateCallback != null) {
|
||||
mDeviceBondStateCallback.onDeviceBondStateChanged(
|
||||
device, BluetoothDevice.BOND_BONDED);
|
||||
@ -541,6 +549,13 @@ class Fakes {
|
||||
mDeviceBondStateCallback = callback;
|
||||
return super.createDeviceBondStateReceiver(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceConnectStateReceiverWrapper createDeviceConnectStateReceiver(
|
||||
DeviceConnectStateReceiverWrapper.Callback callback) {
|
||||
mDeviceConnectStateCallback = callback;
|
||||
return super.createDeviceConnectStateReceiver(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fakes android.content.Context by extending MockContext. */
|
||||
@ -756,6 +771,16 @@ class Fakes {
|
||||
: android.bluetooth.BluetoothProfile.STATE_DISCONNECTED);
|
||||
}
|
||||
|
||||
@CalledByNative("FakeBluetoothDevice")
|
||||
private static void aclConnectionStateChange(
|
||||
ChromeBluetoothDevice chromeDevice, int transport, boolean connected) {
|
||||
FakeBluetoothDevice fakeDevice = (FakeBluetoothDevice) chromeDevice.mDevice;
|
||||
if (fakeDevice.mAdapter.mDeviceConnectStateCallback != null) {
|
||||
fakeDevice.mAdapter.mDeviceConnectStateCallback.onDeviceConnectStateChanged(
|
||||
fakeDevice, transport, connected);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a call to onServicesDiscovered on the |chrome_device| using parameter
|
||||
// |status|.
|
||||
@CalledByNative("FakeBluetoothDevice")
|
||||
|
@ -161,6 +161,17 @@ void BluetoothTestAndroid::UnpairDevice(std::string address) {
|
||||
j_fake_bluetooth_adapter_, address);
|
||||
}
|
||||
|
||||
void BluetoothTestAndroid::SimulateAclConnectStateChange(
|
||||
BluetoothDevice* device,
|
||||
uint8_t transport,
|
||||
bool connected) {
|
||||
BluetoothDeviceAndroid* device_android =
|
||||
static_cast<BluetoothDeviceAndroid*>(device);
|
||||
Java_FakeBluetoothDevice_aclConnectionStateChange(
|
||||
AttachCurrentThread(), device_android->GetJavaObject(), transport,
|
||||
connected);
|
||||
}
|
||||
|
||||
void BluetoothTestAndroid::RememberDeviceForSubsequentAction(
|
||||
BluetoothDevice* device) {
|
||||
BluetoothDeviceAndroid* device_android =
|
||||
|
@ -130,6 +130,11 @@ class BluetoothTestAndroid : public BluetoothTestBase {
|
||||
// Simulates having unpaired a device of |address|.
|
||||
void UnpairDevice(std::string address);
|
||||
|
||||
// Simulates a low level (ACL) connect state change for |device|.
|
||||
void SimulateAclConnectStateChange(BluetoothDevice* device,
|
||||
uint8_t transport,
|
||||
bool connected);
|
||||
|
||||
// Instruct the fake adapter to claim that location services are off for the
|
||||
// device.
|
||||
void SimulateLocationServicesOff();
|
||||
|
Reference in New Issue
Block a user