diff --git a/fastpair/message_stream/BUILD b/fastpair/message_stream/BUILD index 66d60689..a1065b83 100644 --- a/fastpair/message_stream/BUILD +++ b/fastpair/message_stream/BUILD @@ -34,6 +34,7 @@ cc_library( "//internal/platform:base", "//internal/platform:comm", "//internal/platform:types", + "//internal/platform/implementation:types", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", @@ -129,6 +130,7 @@ cc_test( "//internal/platform:logging", "//internal/platform:test_util", "//internal/platform:types", + "//internal/platform/implementation:types", "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/status", diff --git a/fastpair/message_stream/message_stream.cc b/fastpair/message_stream/message_stream.cc index 285019a2..f54b166d 100644 --- a/fastpair/message_stream/message_stream.cc +++ b/fastpair/message_stream/message_stream.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "absl/time/time.h" #include "fastpair/message_stream/message.h" @@ -32,6 +33,13 @@ constexpr uint8_t kSupportsSilenceBit = 0x01; // The default active components response. constexpr uint8_t kDefaultComponents = 0; constexpr int kModelIdSize = 3; +constexpr int kBleAddressSize = 6; +// We don't accept more than `kMaxBatteryLevels` battery values from the +// provider. +constexpr int kMaxBatteryLevels = 3; +constexpr int kUnknownBatteryLevel = 0x7F; +constexpr absl::Duration kActiveComponentsTimeout = absl::Seconds(1); +constexpr absl::Duration kRingAckTimeout = absl::Seconds(1); int GetModelIdFromString(absl::string_view s) { int model_id = static_cast(s[0]); @@ -42,6 +50,17 @@ int GetModelIdFromString(absl::string_view s) { return model_id; } +MessageStream::BatteryInfo ConvertBatteryInfo(uint8_t battery_value) { + // The highest bit represents `is_charging`, the rest the battery level (in + // %). + bool is_charging = battery_value & 0x80; + int level = battery_value & 0x7F; + MessageStream::BatteryInfo battery_info{.is_charging = is_charging}; + if (level != kUnknownBatteryLevel) { + battery_info.level = level; + } + return battery_info; +} } // namespace MessageStream::MessageStream(const FastPairDevice& device, @@ -58,7 +77,7 @@ absl::Status MessageStream::OpenL2cap(absl::string_view ble_address) { absl::Status MessageStream::Disconnect() { return medium_.Disconnect(); } Future MessageStream::GetActiveComponents() { - Future future; + Future future(kActiveComponentsTimeout); absl::Status status = medium_.Send( Message{.message_group = MessageGroup::kDeviceInformationEvent, .message_code = MessageCode::kActiveComponentRequest}); @@ -89,12 +108,20 @@ absl::Status MessageStream::SendCapabilities(bool companion_app_installed, .payload = {capabilites}}); } +absl::Status MessageStream::SendPlatformType( + api::DeviceInfo::OsType platform_type, uint8_t custom_code) { + return medium_.Send( + Message{.message_group = MessageGroup::kDeviceInformationEvent, + .message_code = MessageCode::kPlatformType, + .payload = {static_cast(platform_type), custom_code}}); +} + // Asks the Provider to ring. // Returns true if the Provider replies with an ACK. Future MessageStream::Ring(uint8_t components, absl::Duration duration) { constexpr MessageGroup kGroup = MessageGroup::kDeviceActionEvent; constexpr MessageCode kCode = MessageCode::kRing; - Future future; + Future future(kRingAckTimeout); std::string payload = {components}; uint8_t minutes = absl::ToInt64Minutes(duration); if (minutes != 0) { @@ -179,6 +206,46 @@ bool MessageStream::HandleDeviceInformationEvent(const Message& message) { observer_.OnModelId(model_id); return true; } + case MessageCode::kBleAddressUpdated: { + if (message.payload.size() != kBleAddressSize) { + NEARBY_LOGS(WARNING) + << "BLE address updated event size should be " << kBleAddressSize + << " but is " << message.payload.size(); + break; + } + observer_.OnBleAddressUpdated(message.payload); + return true; + } + case MessageCode::kBatteryUpdated: { + if (message.payload.size() > kMaxBatteryLevels) { + NEARBY_LOGS(WARNING) + << "Battery level event size should be <= " << kMaxBatteryLevels + << " but is " << message.payload.size(); + break; + } + std::vector battery_levels(message.payload.size()); + for (size_t i = 0; i < message.payload.size(); i++) { + battery_levels[i] = ConvertBatteryInfo(message.payload[i]); + } + observer_.OnBatteryUpdated(std::move(battery_levels)); + return true; + } + case MessageCode::kRemainingBatteryTime: { + int battery_time = 0; + if (message.payload.size() == 1) { + battery_time = static_cast(message.payload[0]); + } else if (message.payload.size() == 2) { + battery_time = 256 * static_cast(message.payload[0]) + + static_cast(message.payload[1]); + } else { + NEARBY_LOGS(WARNING) + << "Remaining battery event size should be 1 or 2 bytes but is " + << message.payload.size(); + break; + } + observer_.OnRemainingBatteryTime(absl::Minutes(battery_time)); + return true; + } case MessageCode::kActiveComponentResponse: { uint8_t components = message.payload.size() == 1 ? message.payload.data()[0] diff --git a/fastpair/message_stream/message_stream.h b/fastpair/message_stream/message_stream.h index c1c11383..df84b8ea 100644 --- a/fastpair/message_stream/message_stream.h +++ b/fastpair/message_stream/message_stream.h @@ -16,18 +16,24 @@ #define THIRD_PARTY_NEARBY_FASTPAIR_MESSAGE_STREAM_MESSAGE_STREAM_H_ #include +#include #include #include #include "absl/base/thread_annotations.h" #include "fastpair/message_stream/medium.h" #include "fastpair/message_stream/message.h" +#include "internal/platform/implementation/device_info.h" namespace nearby { namespace fastpair { class MessageStream : public Medium::Observer { public: + struct BatteryInfo { + bool is_charging; + std::optional level; + }; class Observer { public: virtual ~Observer() = default; @@ -43,6 +49,15 @@ class MessageStream : public Medium::Observer { virtual void OnModelId(int model_id) = 0; + virtual void OnBleAddressUpdated(absl::string_view address) = 0; + + virtual void OnBatteryUpdated(std::vector battery_levels) = 0; + + virtual void OnRemainingBatteryTime(absl::Duration duration) = 0; + + // Note, there are no callbacks for Active Components Request and Active + // Components Response. Use `GetActiveComponents()` instead. + virtual bool OnRing(uint8_t components, absl::Duration duration) = 0; }; @@ -63,6 +78,9 @@ class MessageStream : public Medium::Observer { absl::Status SendCapabilities(bool companion_app_installed, bool supports_silence_mode); + absl::Status SendPlatformType(api::DeviceInfo::OsType platform_type, + uint8_t custom_code); + // Asks the Provider to ring. // Returns true if the Provider replies with an ACK. Future Ring(uint8_t components, absl::Duration duration); diff --git a/fastpair/message_stream/message_stream_test.cc b/fastpair/message_stream/message_stream_test.cc index 45ce70b1..8957a11a 100644 --- a/fastpair/message_stream/message_stream_test.cc +++ b/fastpair/message_stream/message_stream_test.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include "gmock/gmock.h" #include "protobuf-matchers/protocol-buffer-matchers.h" @@ -32,6 +33,7 @@ #include "fastpair/message_stream/message.h" #include "internal/platform/bluetooth_classic.h" #include "internal/platform/count_down_latch.h" +#include "internal/platform/implementation/device_info.h" #include "internal/platform/logging.h" #include "internal/platform/medium_environment.h" #include "internal/platform/single_thread_executor.h" @@ -107,6 +109,19 @@ class MessageStreamTest : public testing::Test { void OnModelId(int model_id) override { model_id_.Set(model_id); } + void OnBleAddressUpdated(absl::string_view address) override { + ble_address_updated_.Set(std::string(address)); + } + + void OnBatteryUpdated( + std::vector battery_levels) override { + battery_levels_.Set(battery_levels); + } + + void OnRemainingBatteryTime(absl::Duration duration) override { + remaining_battery_time_.Set(duration); + } + bool OnRing(uint8_t components, absl::Duration duration) override { on_ring_event_.Set({components, duration}); // This allows us to test returning ACK/NACK to the seeker. @@ -115,6 +130,9 @@ class MessageStreamTest : public testing::Test { Future connection_result_; Future disconnected_reason_; Future model_id_; + Future ble_address_updated_; + Future> battery_levels_; + Future remaining_battery_time_; Future silence_mode_; Future log_buffer_full_; struct OnRingData { @@ -202,6 +220,17 @@ TEST_F(MessageStreamTest, RingNacked) { ASSERT_FALSE(result.Get().GetResult()); } +TEST_F(MessageStreamTest, RingNoResponse) { + constexpr uint8_t kComponents = 0x50; + MessageStream message_stream = OpenMessageStream(); + + Future result = message_stream.Ring(kComponents, absl::Minutes(10)); + + VerifySentMessage(absl::HexStringToBytes("04010002500A")); + ASSERT_FALSE(result.Get().ok()); + EXPECT_EQ(result.Get().exception(), Exception::kTimeout); +} + TEST_F(MessageStreamTest, GetActiveComponents) { MessageStream message_stream = OpenMessageStream(); @@ -224,6 +253,16 @@ TEST_F(MessageStreamTest, GetActiveComponentsEmptyResponse) { ASSERT_EQ(result.Get().GetResult(), 0); } +TEST_F(MessageStreamTest, GetActiveComponentsNoResponse) { + MessageStream message_stream = OpenMessageStream(); + + Future result = message_stream.GetActiveComponents(); + + VerifySentMessage(absl::HexStringToBytes("0305")); + ASSERT_FALSE(result.Get().ok()); + EXPECT_EQ(result.Get().exception(), Exception::kTimeout); +} + TEST_F(MessageStreamTest, ReceiveModelId) { MessageStream message_stream = OpenMessageStream(); @@ -308,6 +347,72 @@ TEST_F(MessageStreamTest, OnRingFailSendsNack) { VerifySentMessage(absl::HexStringToBytes("FF0200020401")); } +TEST_F(MessageStreamTest, SendPlatformType) { + MessageStream message_stream = OpenMessageStream(); + + ASSERT_OK( + message_stream.SendPlatformType(api::DeviceInfo::OsType::kAndroid, 0x1C)); + VerifySentMessage(absl::HexStringToBytes("03080002011C")); +} + +TEST_F(MessageStreamTest, ReceiveBleAddressUpdated) { + MessageStream message_stream = OpenMessageStream(); + + provider_.WriteProviderBytes(absl::HexStringToBytes("03020006AABBCCDDEEFF")); + + ASSERT_TRUE(observer_.ble_address_updated_.Get().ok()); + EXPECT_EQ(observer_.ble_address_updated_.Get().GetResult(), + absl::HexStringToBytes("AABBCCDDEEFF")); +} + +TEST_F(MessageStreamTest, ReceiveBatteryUpdated) { + MessageStream message_stream = OpenMessageStream(); + + // The values copied from the specification. + provider_.WriteProviderBytes(absl::HexStringToBytes("0303000357417F")); + + ASSERT_TRUE(observer_.battery_levels_.Get().ok()); + ASSERT_EQ(observer_.battery_levels_.Get().result().size(), 3); + EXPECT_FALSE(observer_.battery_levels_.Get().result()[0].is_charging); + EXPECT_EQ(observer_.battery_levels_.Get().result()[0].level, 87); + EXPECT_FALSE(observer_.battery_levels_.Get().result()[1].is_charging); + EXPECT_EQ(observer_.battery_levels_.Get().result()[1].level, 65); + EXPECT_FALSE(observer_.battery_levels_.Get().result()[2].is_charging); + EXPECT_FALSE(observer_.battery_levels_.Get().result()[2].level.has_value()); +} + +TEST_F(MessageStreamTest, ReceiveBatteryUpdatedOneBattery) { + MessageStream message_stream = OpenMessageStream(); + + provider_.WriteProviderBytes(absl::HexStringToBytes("030300019A")); + + ASSERT_TRUE(observer_.battery_levels_.Get().ok()); + ASSERT_EQ(observer_.battery_levels_.Get().result().size(), 1); + EXPECT_TRUE(observer_.battery_levels_.Get().result()[0].is_charging); + EXPECT_EQ(observer_.battery_levels_.Get().result()[0].level, 0x1A); +} + +TEST_F(MessageStreamTest, ReceiveRemainingBatteryTime) { + MessageStream message_stream = OpenMessageStream(); + + // The values copied from the specification + provider_.WriteProviderBytes(absl::HexStringToBytes("03040001F0")); + + ASSERT_TRUE(observer_.remaining_battery_time_.Get().ok()); + EXPECT_EQ(observer_.remaining_battery_time_.Get().GetResult(), + absl::Minutes(240)); +} + +TEST_F(MessageStreamTest, ReceiveRemainingBatteryTimeHighValue) { + MessageStream message_stream = OpenMessageStream(); + + provider_.WriteProviderBytes(absl::HexStringToBytes("03040002ABCD")); + + ASSERT_TRUE(observer_.remaining_battery_time_.Get().ok()); + EXPECT_EQ(observer_.remaining_battery_time_.Get().GetResult(), + absl::Minutes(0xABCD)); +} + } // namespace } // namespace fastpair } // namespace nearby