From 6d3704f50a6799338ba96fc6cf117275c913ab92 Mon Sep 17 00:00:00 2001 From: Qin Wang Date: Mon, 15 May 2023 12:18:09 -0700 Subject: [PATCH] Add battery notification and not discoverable advertisement PiperOrigin-RevId: 532187475 --- fastpair/common/BUILD | 21 +++ fastpair/common/battery_notification.cc | 86 ++++++++++ fastpair/common/battery_notification.h | 66 ++++++++ fastpair/common/battery_notification_test.cc | 152 ++++++++++++++++++ fastpair/common/constant.h | 8 +- .../common/non_discoverable_advertisement.h | 59 +++++++ 6 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 fastpair/common/battery_notification.cc create mode 100644 fastpair/common/battery_notification.h create mode 100644 fastpair/common/battery_notification_test.cc create mode 100644 fastpair/common/non_discoverable_advertisement.h diff --git a/fastpair/common/BUILD b/fastpair/common/BUILD index b4235e89..ac60ad24 100644 --- a/fastpair/common/BUILD +++ b/fastpair/common/BUILD @@ -3,6 +3,7 @@ licenses(["notice"]) cc_library( name = "common", srcs = [ + "battery_notification.cc", "fast_pair_device.cc", "fast_pair_http_result.cc", "pair_failure.cc", @@ -10,9 +11,11 @@ cc_library( ], hdrs = [ "account_key.h", + "battery_notification.h", "constant.h", "fast_pair_device.h", "fast_pair_http_result.h", + "non_discoverable_advertisement.h", "pair_failure.h", "protocol.h", ], @@ -21,6 +24,7 @@ cc_library( ], deps = [ "//internal/crypto", + "//internal/platform:logging", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", @@ -36,6 +40,7 @@ cc_test( shard_count = 16, deps = [ ":common", + "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", @@ -51,6 +56,22 @@ cc_test( shard_count = 16, deps = [ ":common", + "//internal/platform/implementation/g3", # build_cleaner: keep + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "battery_notification_test", + size = "small", + srcs = [ + "battery_notification_test.cc", + ], + shard_count = 16, + deps = [ + ":common", + "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_googletest//:gtest_main", ], diff --git a/fastpair/common/battery_notification.cc b/fastpair/common/battery_notification.cc new file mode 100644 index 00000000..d46d6b86 --- /dev/null +++ b/fastpair/common/battery_notification.cc @@ -0,0 +1,86 @@ +// Copyright 2023 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/common/battery_notification.h" + +#include +#include +#include + +#include "fastpair/common/constant.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace fastpair { +BatteryInfo::BatteryInfo(bool is_charging) + : is_charging(is_charging), percentage(std::nullopt) {} + +BatteryInfo::BatteryInfo(bool is_charging, int8_t percentage) + : is_charging(is_charging), percentage(percentage) {} + +// static +BatteryInfo BatteryInfo::FromByte(uint8_t byte) { + // Battery value is in the form 0bSVVVVVVV. + // S = charging (0b1) or not (0b0). + // V = value, Ranges from 0-100, or 0bS1111111 if unknown. + bool is_charging = byte & kBatteryChargingMask; + uint8_t percentage = byte & kBatteryPercentageMask; + int8_t percentage_signed = static_cast(percentage); + if (percentage_signed < 0 || percentage_signed > 100) { + NEARBY_LOGS(INFO) << __func__ << "Invalid battery percentage."; + return BatteryInfo(is_charging); + } + return BatteryInfo(is_charging, percentage_signed); +} + +uint8_t BatteryInfo::ToByte() const { + // Battery value is in the form 0bSVVVVVVV. + // S = charging (0b1) or not (0b0). + // V = value, Ranges from 0-100, or 1111111 if unknown. + if (!percentage) { + return is_charging ? kBatteryIsChargingByte : kBatteryNotChargingByte; + } else { + return percentage.value() | (is_charging ? kBatteryChargingMask : 0); + } +} + +BatteryNotification::BatteryNotification( + Type type, const std::vector& battery_infos) + : type(type), battery_infos(battery_infos) {} + +// static +std::optional BatteryNotification::FromBytes( + const std::vector& bytes, Type type) { + if (bytes.size() == 1) { + // Single component device. + NEARBY_LOGS(INFO) << __func__ << " : Single component device."; + std::vector battery_infos = {BatteryInfo::FromByte(bytes[0])}; + return std::make_optional(type, battery_infos); + } else if (bytes.size() == 3) { + // True wireless headset expecting 3 bytes - Left bud, Right bud and case. + NEARBY_LOGS(INFO) << __func__ << " : True wireless headset."; + std::vector battery_infos = { + /* left bud info */ BatteryInfo::FromByte(bytes[0]), + /* right bud info */ BatteryInfo::FromByte(bytes[1]), + /* case info */ BatteryInfo::FromByte(bytes[2])}; + return std::make_optional(type, battery_infos); + } + NEARBY_LOGS(WARNING) << __func__ + << " : Unexpected battery notification length :" + << bytes.size(); + return std::nullopt; +} + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/common/battery_notification.h b/fastpair/common/battery_notification.h new file mode 100644 index 00000000..3c82060f --- /dev/null +++ b/fastpair/common/battery_notification.h @@ -0,0 +1,66 @@ +// Copyright 2023 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_COMMON_BATTERY_NOTIFICATION_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_COMMON_BATTERY_NOTIFICATION_H_ + +#include +#include +#include + +namespace nearby { +namespace fastpair { + +// Fast Pair battery information from notification. See +// https://developers.google.com/nearby/fast-pair/spec#BatteryNotification +struct BatteryInfo { + BatteryInfo() = default; + explicit BatteryInfo(bool is_charging); + BatteryInfo(bool is_charging, int8_t percentage); + ~BatteryInfo() = default; + + static BatteryInfo FromByte(uint8_t byte); + + uint8_t ToByte() const; + + bool is_charging = false; + std::optional percentage; +}; + +// Fast Pair battery notification. See +// https://developers.google.com/nearby/fast-pair/spec#BatteryNotification +struct BatteryNotification { + // Represents if the provider wants to show an indication + // of the battery values + enum class Type { + kNone = 0, + kShowUi = 3, /* Show UI indication: 0b0011 */ + kHideUi = 4, /* Hide UI indication: 0b0100 */ + }; + + BatteryNotification() = default; + BatteryNotification(Type type, const std::vector& battery_infos); + ~BatteryNotification() = default; + + static std::optional FromBytes( + const std::vector& bytes, Type type); + + Type type = Type::kNone; + std::vector battery_infos; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_COMMON_BATTERY_NOTIFICATION_H_ diff --git a/fastpair/common/battery_notification_test.cc b/fastpair/common/battery_notification_test.cc new file mode 100644 index 00000000..48c86b60 --- /dev/null +++ b/fastpair/common/battery_notification_test.cc @@ -0,0 +1,152 @@ +// Copyright 2023 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/common/battery_notification.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace nearby { +namespace fastpair { +namespace { +// Test data comes from: +// https://developers.google.com/nearby/fast-pair/specifications/appendix/testcases#test_cases + +TEST(BatteryNotificationTest, TestBatteryInfo) { + // Tests default constructor + BatteryInfo battery_info_1; + EXPECT_FALSE(battery_info_1.is_charging); + + // Tests Constructor with is_charging + BatteryInfo battery_info_2(true); + EXPECT_TRUE(battery_info_2.is_charging); + // Tests ToByte and FromByte + BatteryInfo battery_info_3 = BatteryInfo::FromByte(battery_info_2.ToByte()); + EXPECT_TRUE(battery_info_3.is_charging); + EXPECT_FALSE(battery_info_3.percentage.has_value()); + + // Tests Constructor with is_charging and percentage value + BatteryInfo battery_info_4(true, 80); + EXPECT_TRUE(battery_info_4.is_charging); + EXPECT_EQ(battery_info_4.percentage.value(), 80); + // Tests ToByte and FromByte + BatteryInfo battery_info_5 = BatteryInfo::FromByte(battery_info_4.ToByte()); + EXPECT_TRUE(battery_info_5.is_charging); + EXPECT_EQ(battery_info_5.percentage.value(), 80); + + // Tests Constructor with is_charging and wrong percentage value + BatteryInfo battery_info_6(true, 110); + // Tests ToByte and FromByte + BatteryInfo battery_info_7 = BatteryInfo::FromByte(battery_info_6.ToByte()); + EXPECT_TRUE(battery_info_7.is_charging); + EXPECT_FALSE(battery_info_7.percentage.has_value()); + + // Tests FromByte with battery percentage = 0 + BatteryInfo battery_info_8 = BatteryInfo::FromByte(0); + EXPECT_FALSE(battery_info_8.is_charging); + EXPECT_EQ(battery_info_8.percentage.value(), 0); + + BatteryInfo battery_info_10 = BatteryInfo::FromByte(0x80); + EXPECT_TRUE(battery_info_10.is_charging); + EXPECT_EQ(battery_info_10.percentage.value(), 0); + + // Tests FromByte with battery percentage = 100 + BatteryInfo battery_info_9 = BatteryInfo::FromByte(100); + EXPECT_FALSE(battery_info_9.is_charging); + EXPECT_EQ(battery_info_9.percentage.value(), 100); + + BatteryInfo battery_info_11 = BatteryInfo::FromByte(0x80 | 100); + EXPECT_TRUE(battery_info_11.is_charging); + EXPECT_EQ(battery_info_11.percentage.value(), 100); + + // Test battery with invalid battery percentage + BatteryInfo battery_info_12 = BatteryInfo::FromByte(0x7F); + EXPECT_FALSE(battery_info_12.is_charging); + EXPECT_FALSE(battery_info_12.percentage.has_value()); + + BatteryInfo battery_info_13 = BatteryInfo::FromByte(0xFF); + EXPECT_TRUE(battery_info_13.is_charging); + EXPECT_FALSE(battery_info_13.percentage.has_value()); +} + +TEST(BatteryNotificationTest, TestBatteryNotificationDefaultConstructor) { + // Tests default constructor + BatteryNotification battery_notification; + EXPECT_EQ(battery_notification.type, BatteryNotification::Type::kNone); + EXPECT_EQ(battery_notification.battery_infos.size(), 0); +} + +TEST(BatteryNotificationTest, TestBatteryNotificationForSingleComponentDevice) { + std::vector battery_infos = {BatteryInfo(false, 70)}; + BatteryNotification battery_notification(BatteryNotification::Type::kShowUi, + battery_infos); + EXPECT_EQ(battery_notification.type, BatteryNotification::Type::kShowUi); + EXPECT_FALSE(battery_notification.battery_infos.at(0).is_charging); + EXPECT_EQ(battery_notification.battery_infos.at(0).percentage.value(), 70); +} + +TEST(BatteryNotificationTest, TestBatteryNotificationForTrueWirelessHeadset) { + std::vector battery_infos = { + BatteryInfo(false, 70), BatteryInfo(false, 80), BatteryInfo(true, 90)}; + BatteryNotification battery_notification(BatteryNotification::Type::kShowUi, + battery_infos); + EXPECT_EQ(battery_notification.type, BatteryNotification::Type::kShowUi); + // Left bud + EXPECT_FALSE(battery_notification.battery_infos.at(0).is_charging); + EXPECT_EQ(battery_notification.battery_infos.at(0).percentage.value(), 70); + // Right bud + EXPECT_FALSE(battery_notification.battery_infos.at(1).is_charging); + EXPECT_EQ(battery_notification.battery_infos.at(1).percentage.value(), 80); + // Case + EXPECT_TRUE(battery_notification.battery_infos.at(2).is_charging); + EXPECT_EQ(battery_notification.battery_infos.at(2).percentage.value(), 90); +} + +TEST(BatteryNotificationTest, + TestBatteryNotificationFromBytesForSingleComponentDevice) { + const std::vector batteryData{0b01000000}; + BatteryNotification battery_notification = + BatteryNotification::FromBytes(batteryData, + BatteryNotification::Type::kShowUi) + .value(); + EXPECT_EQ(battery_notification.type, BatteryNotification::Type::kShowUi); + EXPECT_EQ(battery_notification.battery_infos.at(0).percentage.value(), 64); +} + +TEST(BatteryNotificationTest, + TestBatteryNotificationFromBytesForTrueWirelessHeadset) { + const std::vector batteryData{0b01000000, 0b01000000, 0b01000000}; + BatteryNotification battery_notification = + BatteryNotification::FromBytes(batteryData, + BatteryNotification::Type::kShowUi) + .value(); + EXPECT_EQ(battery_notification.type, BatteryNotification::Type::kShowUi); + EXPECT_EQ(battery_notification.battery_infos.at(0).percentage.value(), 64); + EXPECT_EQ(battery_notification.battery_infos.at(1).percentage.value(), 64); + EXPECT_EQ(battery_notification.battery_infos.at(2).percentage.value(), 64); +} + +TEST(BatteryNotificationTest, TestBatteryNotificationFromWrongBytes) { + const std::vector batteryDataWithWrongLenth{0b01000000, 0b01000000}; + EXPECT_FALSE( + BatteryNotification::FromBytes(batteryDataWithWrongLenth, + BatteryNotification::Type::kShowUi) + .has_value()); +} +} // namespace + +} // namespace fastpair +} // namespace nearby diff --git a/fastpair/common/constant.h b/fastpair/common/constant.h index d4614336..200eb0ac 100644 --- a/fastpair/common/constant.h +++ b/fastpair/common/constant.h @@ -24,7 +24,6 @@ namespace fastpair { constexpr char kServiceId[] = "Fast Pair"; constexpr char kRfcommUuid[] = "df21fe2c-2515-4fdb-8886-f12c4d67927c"; - constexpr int kAccountKeySize = 16; // Key pair @@ -60,6 +59,13 @@ constexpr uint8_t kAccountKeyStartByte = 0x04; constexpr uint8_t kKeyBasedPairingType = 0x00; constexpr uint8_t kInitialOrSubsequentFlags = 0x00; constexpr uint8_t kRetroactiveFlags = 0x10; + +// Battery Info +constexpr int kBatteryChargingMask = 0b10000000; +constexpr int kBatteryPercentageMask = 0b01111111; +constexpr int kBatteryIsChargingByte = 0b11111111; +constexpr int kBatteryNotChargingByte = 0b01111111; + } // namespace fastpair } // namespace nearby diff --git a/fastpair/common/non_discoverable_advertisement.h b/fastpair/common/non_discoverable_advertisement.h new file mode 100644 index 00000000..a03fc421 --- /dev/null +++ b/fastpair/common/non_discoverable_advertisement.h @@ -0,0 +1,59 @@ +// Copyright 2023 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_COMMON_NON_DISCOVERABLE_ADVERTISEMENT_H_ +#define THIRD_PARTY_NEARBY_FASTPAIR_COMMON_NON_DISCOVERABLE_ADVERTISEMENT_H_ + +#include +#include +#include +#include + +#include "fastpair/common/battery_notification.h" + +namespace nearby { +namespace fastpair { + +// Fast Pair 'Not Discoverable' advertisement. See +// https://developers.google.com/nearby/fast-pair/specifications/service/provider#AdvertisingWhenNotDiscoverable +struct NonDiscoverableAdvertisement { + // Represents showing UI indication + enum class Type { + kShowUi = 0, /* Show UI indication: 0b0000*/ + kNone = 1, + kHideUi = 2, /* Hide UI indication: 0b0010*/ + }; + + NonDiscoverableAdvertisement() = default; + NonDiscoverableAdvertisement( + std::vector account_key_filter, Type type, + std::vector salt, + std::optional battery_notification) + : account_key_filter(std::move(account_key_filter)), + type(type), + salt(std::move(salt)), + battery_notification(std::move(battery_notification)) {} + + ~NonDiscoverableAdvertisement() = default; + + std::vector account_key_filter; + Type type = Type::kNone; + std::vector salt; + std::optional battery_notification; +}; + +} // namespace fastpair +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_FASTPAIR_COMMON_NON_DISCOVERABLE_ADVERTISEMENT_H_