0

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:
Garfield Tan
2025-04-09 12:20:03 -07:00
committed by Chromium LUCI CQ
parent 622207aca3
commit 61430495dd
12 changed files with 408 additions and 33 deletions

@ -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);

@ -178,4 +178,9 @@ public class BluetoothAdapterWrapper {
DeviceBondStateReceiverWrapper.Callback callback) {
return new DeviceBondStateReceiverWrapper(callback);
}
public DeviceConnectStateReceiverWrapper createDeviceConnectStateReceiver(
DeviceConnectStateReceiverWrapper.Callback callback) {
return new DeviceConnectStateReceiverWrapper(callback);
}
}

@ -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();