Implement Ble for Fast Pair Windows

PiperOrigin-RevId: 489254207
This commit is contained in:
Qin Wang
2022-11-17 10:58:34 -08:00
committed by Copybara-Service
parent 00fd273eff
commit a9062b958d
6 changed files with 533 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
# Copyright 2022 Google LLC
#
# 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
#
# https://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.
licenses(["notice"])
cc_library(
name = "ble",
srcs = [
"ble.cc",
],
hdrs = [
"ble.h",
],
visibility = [
"//fastpair:__subpackages__",
],
deps = [
"//internal/platform:base",
"//internal/platform:comm",
"//internal/platform:logging",
"//internal/platform:types",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "ble_test",
size = "small",
srcs = [
"ble_test.cc",
],
shard_count = 16,
deps = [
":ble",
"//internal/platform:base",
"//internal/platform:comm",
"//internal/platform:test_util",
"//internal/platform:types",
"//internal/platform/implementation/g3", # build_cleaner: keep
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
],
)
+208
View File
@@ -0,0 +1,208 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.
#include "fastpair/internal/ble/ble.h"
#include <memory>
#include <string>
#include <utility>
#include "internal/platform/exception.h"
#include "internal/platform/logging.h"
#include "internal/platform/system_clock.h"
namespace location {
namespace nearby {
namespace fastpair {
constexpr absl::Duration Ble::kPauseBetweenToggle;
Ble::~Ble() {
// We never enabled Bluetooth, nothing to do.
if (!ever_saved_state_.Get()) {
NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW.");
return;
}
NEARBY_LOG(INFO, "Bring BT adapter to original state");
if (!SetBluetoothState(originally_enabled_.Get())) {
NEARBY_LOG(INFO, "Failed to restore BT adapter original state.");
}
}
bool Ble::Enable() {
if (!SaveOriginalState()) {
return false;
}
return SetBluetoothState(true);
}
bool Ble::Disable() {
if (!SaveOriginalState()) {
return false;
}
return SetBluetoothState(false);
}
bool Ble::IsEnabled() const {
return IsAdapterValid() && IsInDesiredState(true);
}
bool Ble::Toggle() {
if (!SaveOriginalState()) {
return false;
}
if (!SetBluetoothState(false)) {
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off.");
return false;
}
if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) {
NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on.");
return false;
}
if (!SetBluetoothState(true)) {
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on.");
return false;
}
return true;
}
bool Ble::SetBluetoothState(bool enable) {
return adapter_.SetStatus(enable ? BluetoothAdapter::Status::kEnabled
: BluetoothAdapter::Status::kDisabled);
}
bool Ble::IsInDesiredState(bool should_be_enabled) const {
return adapter_.IsEnabled() == should_be_enabled;
}
bool Ble::SaveOriginalState() {
if (!IsAdapterValid()) {
return false;
}
// If we haven't saved the original state of the radio, save it.
if (!ever_saved_state_.Set(true)) {
originally_enabled_.Set(adapter_.IsEnabled());
}
return true;
}
bool Ble::IsAvailable() const {
MutexLock lock(&mutex_);
return IsAvailableLocked();
}
bool Ble::IsAvailableLocked() const {
return medium_.IsValid() && adapter_.IsValid() && adapter_.IsEnabled();
}
bool Ble::StartScanning(const std::string& service_id,
const std::string& fast_pair_service_uuid,
DiscoveredPeripheralCallback callback) {
MutexLock lock(&mutex_);
discovered_peripheral_callback_ = std::move(callback);
if (service_id.empty()) {
NEARBY_LOGS(INFO)
<< "Refusing to start BLE scanning with empty service id.";
return false;
}
if (IsScanningLocked()) {
NEARBY_LOGS(INFO) << "Refusing to start BLE scanning because "
"another scanning is already in-progress.";
return false;
}
if (!IsEnabled()) {
NEARBY_LOGS(INFO)
<< "Can't start BLE scanning because Bluetooth was NOT enabled";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO) << "Can't scan BLE scanning because BLE isn't available.";
return false;
}
if (!medium_.StartScanning(
service_id, fast_pair_service_uuid,
{
.peripheral_discovered_cb =
[this](BlePeripheral& peripheral,
const std::string& service_id,
const ByteArray& medium_advertisement_bytes,
bool fast_advertisement) {
// Don't bother trying to parse zero byte advertisements.
if (medium_advertisement_bytes.size() == 0) {
NEARBY_LOGS(INFO) << "Skipping zero byte advertisement "
<< "with service_id: " << service_id;
return;
}
discovered_peripheral_callback_.peripheral_discovered_cb(
peripheral, service_id, medium_advertisement_bytes,
fast_advertisement);
},
.peripheral_lost_cb =
[this](BlePeripheral& peripheral,
const std::string& service_id) {
discovered_peripheral_callback_.peripheral_lost_cb(
peripheral, service_id);
},
})) {
NEARBY_LOGS(INFO) << "Failed to start BLE scanning";
return false;
}
NEARBY_LOGS(INFO) << "Start BLE scanning with service id=" << service_id;
// Mark the fact that we're currently performing a BLE scanning.
is_scanning_ = true;
return true;
}
bool Ble::StopScanning(const std::string& service_id) {
MutexLock lock(&mutex_);
if (!IsScanningLocked()) {
NEARBY_LOGS(INFO) << "Can't stop BLE scanning because we never "
"started scanning.";
return false;
}
NEARBY_LOG(INFO, "Stop BLE scanning with service id=%s", service_id.c_str());
bool ret = medium_.StopScanning(service_id);
is_scanning_ = false;
return ret;
}
bool Ble::IsScanning() {
MutexLock lock(&mutex_);
return IsScanningLocked();
}
bool Ble::IsScanningLocked() { return is_scanning_; }
} // namespace fastpair
} // namespace nearby
} // namespace location
+130
View File
@@ -0,0 +1,130 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.
#ifndef THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_BLE_BLE_H_
#define THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_BLE_BLE_H_
#include <cstdint>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/time/clock.h"
#include "internal/platform/atomic_boolean.h"
#include "internal/platform/ble.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/multi_thread_executor.h"
#include "internal/platform/mutex.h"
namespace location {
namespace nearby {
namespace fastpair {
class Ble {
public:
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
Ble() = default;
Ble(Ble&&) = default;
Ble& operator=(Ble&&) = default;
// Reverts the Ble to its original state.
~Ble();
// Enables Bluetooth. Returns true if enabled successfully.
// This must be called before attempting to invoke any other methods of
// this class.
bool Enable();
// Disables Bluetooth. Returns true if disabled successfully.
bool Disable();
// Returns true if the Bluetooth radio is currently enabled.
bool IsEnabled() const;
// Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On.
// This will block calling thread for at least kPauseBetweenToggle duration.
bool Toggle();
// Returns true if Ble communications are supported by a platform.
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
// Returns true if this object owns a valid platform implementation.
bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
MutexLock lock(&mutex_);
return medium_.IsValid();
}
// Returns true if this object has a valid BluetoothAdapter reference.
bool IsAdapterValid() const { return adapter_.IsValid(); }
// Return true if Ble is currenlty scanning.
bool IsScanning() ABSL_LOCKS_EXCLUDED(mutex_);
// Enables Ble scanning mode. Will report any discoverable peripherals in
// range through a callback. Returns true, if scanning mode was enabled,
// false otherwise.
bool StartScanning(const std::string& service_id,
const std::string& fast_pair_service_uuid,
DiscoveredPeripheralCallback callback)
ABSL_LOCKS_EXCLUDED(mutex_);
// Disables Ble discovery mode.
bool StopScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
// Return BleMedium
BleMedium& getMedium() { return medium_; }
// Return BluetoothAdapter
BluetoothAdapter& GetBluetoothAdapter() { return adapter_; }
private:
mutable Mutex mutex_;
// BluetoothAdapter::IsValid() will return false if BT is not supported.
BluetoothAdapter adapter_;
DiscoveredPeripheralCallback discovered_peripheral_callback_;
BleMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_};
bool is_scanning_ = false;
static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3);
// Same as IsAvailable(), but must be called with mutex_ held.
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Same as IsDiscovering(), but must be called with mutex_ held.
bool IsScanningLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
bool SetBluetoothState(bool enable);
bool IsInDesiredState(bool should_be_enabled) const;
// To be called in enable(), disable(), and toggle(). This will remember the
// original state of the ble before any ble state has been modified.
// Returns false if Bluetooth doesn't exist on the device and the state cannot
// be obtained.
bool SaveOriginalState();
// The Ble's original state, before we modified it. True if
// originally enabled, false if originally disabled.
// We restore the radio to its original state in the destructor.
AtomicBoolean originally_enabled_{false};
// false if we never modified the radio state, true otherwise.
AtomicBoolean ever_saved_state_{false};
};
} // namespace fastpair
} // namespace nearby
} // namespace location
#endif // THIRD_PARTY_NEARBY_FASTPAIR_INTERNAL_BLE_BLE_H_
+129
View File
@@ -0,0 +1,129 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.
#include "fastpair/internal/ble/ble.h"
#include <string>
#include "gtest/gtest.h"
#include "internal/platform/ble.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/medium_environment.h"
namespace location {
namespace nearby {
namespace fastpair {
namespace {
using FeatureFlags = FeatureFlags::Flags;
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"};
constexpr absl::string_view kAdvertisementString{"96C12E"};
constexpr absl::string_view kFastPairServiceUuid{"\x2c\xfe"};
class BleTest : public ::testing::TestWithParam<FeatureFlags> {
protected:
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
TEST_F(BleTest, ConstructorDestructorWorks) {
Ble ble;
EXPECT_TRUE(ble.IsAdapterValid());
}
TEST_F(BleTest, CanEnable) {
Ble ble;
EXPECT_TRUE(ble.IsAdapterValid());
EXPECT_FALSE(ble.IsEnabled());
EXPECT_TRUE(ble.Enable());
EXPECT_TRUE(ble.IsEnabled());
}
TEST_F(BleTest, CanDisable) {
Ble ble;
EXPECT_TRUE(ble.IsAdapterValid());
EXPECT_FALSE(ble.IsEnabled());
EXPECT_TRUE(ble.Enable());
EXPECT_TRUE(ble.IsEnabled());
EXPECT_TRUE(ble.Disable());
EXPECT_FALSE(ble.IsEnabled());
}
TEST_F(BleTest, CanToggle) {
Ble ble;
EXPECT_TRUE(ble.IsAdapterValid());
EXPECT_FALSE(ble.IsEnabled());
EXPECT_TRUE(ble.Toggle());
EXPECT_TRUE(ble.IsEnabled());
}
TEST_F(BleTest, CanConstructValidObject) {
env_.Start();
Ble ble_a;
Ble ble_b;
EXPECT_TRUE(ble_a.IsMediumValid());
EXPECT_TRUE(ble_a.IsAdapterValid());
EXPECT_FALSE(ble_a.IsAvailable());
EXPECT_TRUE(ble_b.IsMediumValid());
EXPECT_TRUE(ble_b.IsAdapterValid());
EXPECT_FALSE(ble_b.IsAvailable());
EXPECT_NE(&ble_a.GetBluetoothAdapter(), &ble_b.GetBluetoothAdapter());
env_.Stop();
}
TEST_F(BleTest, CanStartDiscovery) {
env_.Start();
Ble ble_a;
Ble ble_b;
ble_a.Enable();
ble_b.Enable();
std::string service_id(kServiceID);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_pair_service_uuid(kFastPairServiceUuid);
CountDownLatch accept_latch(1);
CountDownLatch lost_latch(1);
ble_b.getMedium().StartAdvertising(service_id, advertisement_bytes,
fast_pair_service_uuid);
EXPECT_TRUE(ble_a.StartScanning(
service_id, fast_pair_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
[&accept_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { accept_latch.CountDown(); },
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
},
}));
EXPECT_TRUE(ble_a.IsScanning());
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
ble_b.getMedium().StopAdvertising(service_id);
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopScanning(service_id));
EXPECT_FALSE(ble_a.IsScanning());
env_.Stop();
}
} // namespace
} // namespace fastpair
} // namespace nearby
} // namespace location
+6
View File
@@ -46,6 +46,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//internal:__pkg__",
"//internal/analytics:__subpackages__",
"//internal/platform:__subpackages__",
@@ -102,6 +103,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__",
"//internal/analytics:__subpackages__",
"//internal/crypto:__subpackages__",
@@ -194,6 +196,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections/implementation:__subpackages__",
"//fastpair:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//presence:__subpackages__",
@@ -216,6 +219,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//presence:__subpackages__",
@@ -335,6 +339,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//internal/analytics:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation/ios:__subpackages__",
@@ -384,6 +389,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__",
"//internal/analytics:__subpackages__",
"//internal/platform:__pkg__",
@@ -119,6 +119,7 @@ cc_library(
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//fastpair:__subpackages__",
"//internal/analytics:__subpackages__",
"//internal/crypto:__subpackages__",
"//internal/platform:__subpackages__",