Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6deefa3 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Automerger Merge Worker
Browse files

groups: Add module to manage devices groups am: 1aa8941d am: 1c153d6b am:...

groups: Add module to manage devices groups am: 1aa8941d am: 1c153d6b am: b7a7260b am: 63bcfeb2

Original change: https://googleplex-android-review.googlesource.com/c/platform/system/bt/+/15795707

Change-Id: I213c850c2333b723a76ca01105568778cca9920d
parents ae40c528 63bcfeb2
Loading
Loading
Loading
Loading
+42 −0
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ cc_library_static {
        "gatt/bta_gatts_utils.cc",
        "gatt/database.cc",
        "gatt/database_builder.cc",
        "groups/groups.cc",
        "vc/device.cc",
        "vc/vc.cc",
        "hearing_aid/hearing_aid.cc",
@@ -258,6 +259,47 @@ cc_test {
}


// groups unit tests for host
cc_test {
    name: "bluetooth_groups_test",
    test_suites: ["device-tests"],
    defaults: [
        "fluoride_bta_defaults",
        "clang_coverage_bin",
    ],
    host_supported: true,
    include_dirs: [
        "packages/modules/Bluetooth/system",
        "packages/modules/Bluetooth/system/bta/include",
    ],
    srcs : [
        ":TestMockBtif",
        "groups/groups_test.cc",
        "groups/groups.cc",
    ],
    shared_libs: [
        "libprotobuf-cpp-lite",
        "libcrypto",
    ],
    static_libs : [
        "crypto_toolbox_for_tests",
        "libgmock",
        "libbt-common",
        "libbt-protos-lite",
        "libosi",
    ],
    sanitize: {
        cfi: true,
        scs: true,
        address: true,
        all_undefined: true,
        integer_overflow: true,
        diag: {
            undefined : true
        },
    },
}

// bta unit tests for host
cc_test {
    name: "bluetooth_vc_test",
+1 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ static_library("bta") {
    "gatt/bta_gatts_utils.cc",
    "gatt/database.cc",
    "gatt/database_builder.cc",
    "groups/groups.cc",
    "hearing_aid/hearing_aid.cc",
    "hearing_aid/hearing_aid_audio_source.cc",
    "hf_client/bta_hf_client_act.cc",
+335 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * 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
 *
 *      http://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 <algorithm>
#include <map>
#include <unordered_set>

#include "base/logging.h"
#include "bta_groups.h"
#include "btif_storage.h"
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"

using bluetooth::Uuid;

namespace bluetooth {
namespace groups {

class DeviceGroupsImpl;
DeviceGroupsImpl* instance;
static constexpr int kMaxGroupId = 0xEF;

class DeviceGroup {
 public:
  DeviceGroup(int group_id, Uuid uuid)
      : group_id_(group_id), group_uuid_(uuid) {}
  void Add(const RawAddress& addr) { devices_.insert(addr); }
  void Remove(const RawAddress& addr) { devices_.erase(addr); }
  bool Contains(const RawAddress& addr) const {
    return (devices_.count(addr) != 0);
  }

  void ForEachDevice(std::function<void(const RawAddress&)> cb) const {
    for (auto const& addr : devices_) {
      cb(addr);
    }
  }

  int Size(void) const { return devices_.size(); }
  int GetGroupId(void) const { return group_id_; }
  const Uuid& GetUuid(void) const { return group_uuid_; }

 private:
  int group_id_;
  Uuid group_uuid_;
  std::unordered_set<RawAddress> devices_;
};

class DeviceGroupsImpl : public DeviceGroups {
  static constexpr uint8_t GROUP_STORAGE_CURRENT_LAYOUT_MAGIC = 0x10;
  static constexpr size_t GROUP_STORAGE_HEADER_SZ =
      sizeof(GROUP_STORAGE_CURRENT_LAYOUT_MAGIC) +
      sizeof(uint8_t); /* num_of_groups */
  static constexpr size_t GROUP_STORAGE_ENTRY_SZ =
      sizeof(uint8_t) /* group_id */ + Uuid::kNumBytes128;

 public:
  DeviceGroupsImpl(DeviceGroupsCallbacks* callbacks) {
    AddCallbacks(callbacks);
    btif_storage_load_bonded_groups();
  }

  int GetGroupId(const RawAddress& addr, Uuid uuid) const override {
    for (const auto& [id, g] : groups_) {
      if ((g.Contains(addr)) && (uuid == g.GetUuid())) return id;
    }
    return kGroupUnknown;
  }

  void add_to_group(const RawAddress& addr, DeviceGroup* group) {
    group->Add(addr);

    bool first_device_in_group = (group->Size() == 1);

    for (auto c : callbacks_) {
      if (first_device_in_group) {
        c->OnGroupAdded(addr, group->GetUuid(), group->GetGroupId());
      } else {
        c->OnGroupMemberAdded(addr, group->GetGroupId());
      }
    }
  }

  int AddDevice(const RawAddress& addr, Uuid uuid, int group_id) override {
    DeviceGroup* group = nullptr;

    if (group_id == kGroupUnknown) {
      auto gid = GetGroupId(addr, uuid);
      if (gid != kGroupUnknown) return gid;
      group = create_group(uuid);
    } else {
      group = get_or_create_group_with_id(group_id, uuid);
      if (!group) {
        return kGroupUnknown;
      }
    }

    LOG_ASSERT(group);

    if (group->Contains(addr)) {
      LOG(ERROR) << __func__ << " device " << addr
                 << " already in the group: " << group_id;
      return group->GetGroupId();
    }

    add_to_group(addr, group);

    btif_storage_add_groups(addr);
    return group->GetGroupId();
  }

  void RemoveDevice(const RawAddress& addr) override {
    for (auto it = groups_.begin(); it != groups_.end();) {
      auto& [id, g] = *it;
      if (!g.Contains(addr)) {
        ++it;
        continue;
      }

      g.Remove(addr);
      for (auto c : callbacks_) {
        c->OnGroupMemberRemoved(addr, id);
      }

      if (g.Size() == 0) {
        for (auto c : callbacks_) {
          c->OnGroupRemoved(g.GetUuid(), g.GetGroupId());
        }
        it = groups_.erase(it);
      } else {
        ++it;
      }
    }

    btif_storage_remove_groups(addr);
  }

  bool SerializeGroups(const RawAddress& addr,
                       std::vector<uint8_t>& out) const {
    auto num_groups = std::count_if(
        groups_.begin(), groups_.end(), [&addr](auto& id_group_pair) {
          return id_group_pair.second.Contains(addr);
        });
    if ((num_groups == 0) || (num_groups > std::numeric_limits<uint8_t>::max()))
      return false;

    out.resize(GROUP_STORAGE_HEADER_SZ + (num_groups * GROUP_STORAGE_ENTRY_SZ));
    auto* ptr = out.data();

    /* header */
    UINT8_TO_STREAM(ptr, GROUP_STORAGE_CURRENT_LAYOUT_MAGIC);
    UINT8_TO_STREAM(ptr, num_groups);

    /* group entries */
    for (const auto& [id, g] : groups_) {
      if (g.Contains(addr)) {
        UINT8_TO_STREAM(ptr, id);

        Uuid::UUID128Bit uuid128 = g.GetUuid().To128BitLE();
        memcpy(ptr, uuid128.data(), Uuid::kNumBytes128);
        ptr += Uuid::kNumBytes128;
      }
    }

    return true;
  }

  void DeserializeGroups(const RawAddress& addr,
                         const std::vector<uint8_t>& in) {
    if (in.size() < GROUP_STORAGE_HEADER_SZ + GROUP_STORAGE_ENTRY_SZ) return;

    auto* ptr = in.data();

    uint8_t magic;
    STREAM_TO_UINT8(magic, ptr);

    if (magic == GROUP_STORAGE_CURRENT_LAYOUT_MAGIC) {
      uint8_t num_groups;
      STREAM_TO_UINT8(num_groups, ptr);

      if (in.size() <
          GROUP_STORAGE_HEADER_SZ + (num_groups * GROUP_STORAGE_ENTRY_SZ)) {
        LOG(ERROR) << "Invalid persistent storage data";
        return;
      }

      /* group entries */
      while (num_groups--) {
        uint8_t id;
        STREAM_TO_UINT8(id, ptr);

        Uuid::UUID128Bit uuid128;
        STREAM_TO_ARRAY(uuid128.data(), ptr, (int)Uuid::kNumBytes128);

        auto* group =
            get_or_create_group_with_id(id, Uuid::From128BitLE(uuid128));
        if (group) add_to_group(addr, group);
      }
    }
  }

  void AddCallbacks(DeviceGroupsCallbacks* callbacks) {
    callbacks_.push_back(std::move(callbacks));

    /* Notify new user about known groups */
    for (const auto& [id, g] : groups_) {
      auto group_uuid = g.GetUuid();
      auto group_id = g.GetGroupId();
      g.ForEachDevice([&](auto& dev) {
        callbacks->OnGroupAdded(dev, group_uuid, group_id);
      });
    }
  }

  bool Clear(DeviceGroupsCallbacks* callbacks) {
    auto it = find_if(callbacks_.begin(), callbacks_.end(),
                      [callbacks](auto c) { return c == callbacks; });

    if (it != callbacks_.end()) callbacks_.erase(it);

    if (callbacks_.size() != 0) {
      return false;
    }
    /* When all clients were unregistered */
    groups_.clear();
    return true;
  }

 private:
  DeviceGroup* find_device_group(int group_id) {
    return groups_.count(group_id) ? &groups_.at(group_id) : nullptr;
  }

  DeviceGroup* get_or_create_group_with_id(int group_id, Uuid uuid) {
    auto group = find_device_group(group_id);
    if (group) {
      if (group->GetUuid() != uuid) {
        LOG(ERROR) << __func__ << " group " << group_id
                   << " exists but for different uuid: " << group->GetUuid()
                   << ", user request uuid: " << uuid;
        return nullptr;
      }

      LOG(INFO) << __func__ << " group already exists: " << group_id;
      return group;
    }

    DeviceGroup new_group(group_id, uuid);
    groups_.insert({group_id, std::move(new_group)});

    return &groups_.at(group_id);
  }

  DeviceGroup* create_group(Uuid& uuid) {
    /* Generate new group id and return empty group */
    /* Find first free id */

    int group_id = -1;
    for (int i = 1; i < kMaxGroupId; i++) {
      if (groups_.count(i) == 0) {
        group_id = i;
        break;
      }
    }

    if (group_id < 0) {
      LOG(ERROR) << __func__ << " too many groups";
      return nullptr;
    }

    DeviceGroup group(group_id, uuid);
    groups_.insert({group_id, std::move(group)});

    return &groups_.at(group_id);
  }

  std::map<int, DeviceGroup> groups_;
  std::list<DeviceGroupsCallbacks*> callbacks_;
};

void DeviceGroups::Initialize(DeviceGroupsCallbacks* callbacks) {
  if (instance == nullptr) {
    instance = new DeviceGroupsImpl(callbacks);
    return;
  }

  instance->AddCallbacks(callbacks);
}

void DeviceGroups::AddFromStorage(const RawAddress& addr,
                                  const std::vector<uint8_t>& in) {
  if (!instance) {
    LOG(ERROR) << __func__ << ": Not initialized yet";
    return;
  }

  instance->DeserializeGroups(addr, in);
}

bool DeviceGroups::GetForStorage(const RawAddress& addr,
                                 std::vector<uint8_t>& out) {
  if (!instance) {
    LOG(ERROR) << __func__ << ": Not initialized yet";
    return false;
  }

  return instance->SerializeGroups(addr, out);
}

void DeviceGroups::CleanUp(DeviceGroupsCallbacks* callbacks) {
  if (!instance) return;

  if (instance->Clear(callbacks)) {
    delete (instance);
    instance = nullptr;
  }
}
DeviceGroups* DeviceGroups::Get() { return instance; }

}  // namespace groups
}  // namespace bluetooth
+288 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * 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
 *
 *      http://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 <base/logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "bta_groups.h"
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"

std::map<std::string, int> mock_function_count_map;

namespace bluetooth {
namespace groups {

using ::testing::_;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::Test;

using bluetooth::groups::DeviceGroups;
using bluetooth::groups::DeviceGroupsCallbacks;

DeviceGroupsCallbacks* dev_callbacks;

RawAddress GetTestAddress(int index) {
  CHECK_LT(index, UINT8_MAX);
  RawAddress result = {
      {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}};
  return result;
}

class MockGroupsCallbacks : public DeviceGroupsCallbacks {
 public:
  MockGroupsCallbacks() = default;
  ~MockGroupsCallbacks() override = default;

  MOCK_METHOD((void), OnGroupAdded,
              (const RawAddress& address, const bluetooth::Uuid& uuid,
               int group_id),
              (override));
  MOCK_METHOD((void), OnGroupMemberAdded,
              (const RawAddress& address, int group_id), (override));

  MOCK_METHOD((void), OnGroupRemoved,
              (const bluetooth::Uuid& uuid, int group_id), (override));
  MOCK_METHOD((void), OnGroupMemberRemoved,
              (const RawAddress& address, int group_id), (override));

 private:
  DISALLOW_COPY_AND_ASSIGN(MockGroupsCallbacks);
};

class GroupsTest : public ::testing::Test {
 protected:
  void SetUp() override {
    mock_function_count_map.clear();
    callbacks.reset(new MockGroupsCallbacks());
  }

  void TearDown() override { DeviceGroups::CleanUp(callbacks.get()); }

  std::unique_ptr<MockGroupsCallbacks> callbacks;
};

TEST_F(GroupsTest, test_initialize) {
  DeviceGroups::Initialize(callbacks.get());
  ASSERT_TRUE(DeviceGroups::Get());
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_initialize_twice) {
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups* dev_groups_p = DeviceGroups::Get();
  DeviceGroups::Initialize(callbacks.get());
  ASSERT_EQ(dev_groups_p, DeviceGroups::Get());
  DeviceGroups::CleanUp(callbacks.get());
  dev_groups_p->CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_cleanup_initialized) {
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::CleanUp(callbacks.get());
  ASSERT_FALSE(DeviceGroups::Get());
}

TEST_F(GroupsTest, test_cleanup_uninitialized) {
  DeviceGroups::CleanUp(callbacks.get());
  ASSERT_FALSE(DeviceGroups::Get());
}

TEST_F(GroupsTest, test_groups_add_single_device) {
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(1), Uuid::kEmpty, 7));
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 7);
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_groups_add_two_devices) {
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(1), _, 7));
  EXPECT_CALL(*callbacks, OnGroupMemberAdded(GetTestAddress(2), 7));
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 7);
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_groups_remove_device) {
  EXPECT_CALL(*callbacks, OnGroupMemberRemoved(GetTestAddress(2), 7));
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 7);
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(2));
  ASSERT_EQ(kGroupUnknown,
            DeviceGroups::Get()->GetGroupId(GetTestAddress(2), Uuid::kEmpty));
  ASSERT_EQ(kGroupUnknown,
            DeviceGroups::Get()->GetGroupId(GetTestAddress(3), Uuid::kEmpty));
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_add_multiple_devices) {
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(2), Uuid::kEmpty, 7));
  EXPECT_CALL(*callbacks, OnGroupMemberAdded(_, 7)).Times(2);
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(3), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(4), Uuid::kEmpty, 7);
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_remove_multiple_devices) {
  EXPECT_CALL(*callbacks, OnGroupMemberRemoved(_, _)).Times(3);
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(3), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(4), Uuid::kEmpty, 7);
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(2));
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(3));
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(4));
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_add_multiple_groups) {
  EXPECT_CALL(*callbacks, OnGroupAdded(_, _, _)).Times(2);
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 8);
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 9);
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_remove_multiple_groups) {
  Uuid uuid1 = Uuid::GetRandom();
  Uuid uuid2 = Uuid::GetRandom();
  ASSERT_NE(uuid1, uuid2);

  EXPECT_CALL(*callbacks, OnGroupAdded(_, _, _)).Times(2);
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), uuid1, 8);
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), uuid2, 9);
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), uuid2, 9);

  EXPECT_CALL(*callbacks, OnGroupMemberRemoved(GetTestAddress(1), 8));
  EXPECT_CALL(*callbacks, OnGroupMemberRemoved(GetTestAddress(1), 9));
  EXPECT_CALL(*callbacks, OnGroupRemoved(uuid1, 8));
  EXPECT_CALL(*callbacks, OnGroupRemoved(uuid2, 9)).Times(0);
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(1));

  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_add_devices_different_group_id) {
  DeviceGroups::Initialize(callbacks.get());
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 10);
  DeviceGroups::Get()->AddDevice(GetTestAddress(3), Uuid::kEmpty, 11);
  auto group_id_1 =
      DeviceGroups::Get()->GetGroupId(GetTestAddress(2), Uuid::kEmpty);
  auto group_id_2 =
      DeviceGroups::Get()->GetGroupId(GetTestAddress(3), Uuid::kEmpty);
  ASSERT_TRUE(group_id_1 != group_id_2);
  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_group_id_assign) {
  int captured_gid1 = kGroupUnknown;
  int captured_gid2 = kGroupUnknown;

  DeviceGroups::Initialize(callbacks.get());
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(1), _, _))
      .WillOnce(SaveArg<2>(&captured_gid1));
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(2), _, _))
      .WillOnce(SaveArg<2>(&captured_gid2));

  int gid1 = DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty,
                                            bluetooth::groups::kGroupUnknown);
  int gid2 = DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty);
  int gid3 = DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty);
  ASSERT_NE(bluetooth::groups::kGroupUnknown, gid1);
  ASSERT_NE(bluetooth::groups::kGroupUnknown, gid2);
  ASSERT_EQ(gid2, gid3);
  ASSERT_EQ(gid1, captured_gid1);
  ASSERT_EQ(gid2, captured_gid2);

  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_storage_calls) {
  ASSERT_EQ(0, mock_function_count_map["btif_storage_load_bonded_groups"]);
  DeviceGroups::Initialize(callbacks.get());
  ASSERT_EQ(1, mock_function_count_map["btif_storage_load_bonded_groups"]);

  ASSERT_EQ(0, mock_function_count_map["btif_storage_add_groups"]);
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(1), Uuid::kEmpty, 8);
  ASSERT_EQ(2, mock_function_count_map["btif_storage_add_groups"]);

  DeviceGroups::Get()->AddDevice(GetTestAddress(2), Uuid::kEmpty, 7);
  DeviceGroups::Get()->AddDevice(GetTestAddress(3), Uuid::kEmpty, 7);
  ASSERT_EQ(4, mock_function_count_map["btif_storage_add_groups"]);

  ASSERT_EQ(0, mock_function_count_map["btif_storage_remove_groups"]);
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(1));
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(2));
  DeviceGroups::Get()->RemoveDevice(GetTestAddress(3));
  ASSERT_EQ(3, mock_function_count_map["btif_storage_remove_groups"]);

  DeviceGroups::CleanUp(callbacks.get());
}

TEST_F(GroupsTest, test_storage_content) {
  int gid1 = bluetooth::groups::kGroupUnknown;
  int gid2 = bluetooth::groups::kGroupUnknown;
  Uuid uuid1 = Uuid::GetRandom();
  Uuid uuid2 = Uuid::GetRandom();
  ASSERT_NE(uuid1, uuid2);

  DeviceGroups::Initialize(callbacks.get());
  gid1 = DeviceGroups::Get()->AddDevice(GetTestAddress(1), uuid1, gid1);
  DeviceGroups::Get()->AddDevice(GetTestAddress(2), uuid1, gid1);
  gid2 = DeviceGroups::Get()->AddDevice(GetTestAddress(2), uuid2, gid2);
  ASSERT_NE(bluetooth::groups::kGroupUnknown, gid1);
  ASSERT_NE(gid1, gid2);

  std::vector<uint8_t> dev1_storage;
  std::vector<uint8_t> dev2_storage;

  // Store to byte buffer
  DeviceGroups::GetForStorage(GetTestAddress(1), dev1_storage);
  DeviceGroups::GetForStorage(GetTestAddress(2), dev2_storage);
  ASSERT_NE(0u, dev1_storage.size());
  ASSERT_TRUE(dev2_storage.size() > dev1_storage.size());

  // Clean it up
  DeviceGroups::CleanUp(callbacks.get());
  ASSERT_EQ(nullptr, DeviceGroups::Get());

  // Restore dev1 from the byte buffer
  DeviceGroups::Initialize(callbacks.get());
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(1), uuid1, gid1));
  DeviceGroups::AddFromStorage(GetTestAddress(1), dev1_storage);

  // Restore dev2 from the byte buffer
  EXPECT_CALL(*callbacks, OnGroupAdded(GetTestAddress(2), uuid2, gid2));
  EXPECT_CALL(*callbacks, OnGroupMemberAdded(GetTestAddress(2), gid1)).Times(1);
  DeviceGroups::AddFromStorage(GetTestAddress(2), dev2_storage);

  DeviceGroups::CleanUp(callbacks.get());
}

}  // namespace groups
}  // namespace bluetooth
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * 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
 *
 *      http://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.
 */

#pragma once
#include <list>

#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"

namespace bluetooth {
namespace groups {

static constexpr int kGroupUnknown = -1;
static const bluetooth::Uuid kGenericContextUuid =
    bluetooth::Uuid::From16Bit(0x0000);

class DeviceGroupsCallbacks {
 public:
  virtual ~DeviceGroupsCallbacks() = default;

  /* Notifies first group appearance.
   * This callback also contains first group member and uuid of the group.
   */
  virtual void OnGroupAdded(const RawAddress& address,
                            const bluetooth::Uuid& group_uuid,
                            int group_id) = 0;

  /* Followed group members are notified with this callback */
  virtual void OnGroupMemberAdded(const RawAddress& address, int group_id) = 0;

  /* Group removal callback */
  virtual void OnGroupRemoved(const bluetooth::Uuid& group_uuid,
                              int group_id) = 0;

  /* Callback with group status update */
  virtual void OnGroupMemberRemoved(const RawAddress& address,
                                    int group_id) = 0;
};

class DeviceGroups {
 public:
  virtual ~DeviceGroups() = default;
  static void Initialize(DeviceGroupsCallbacks* callbacks);
  static void AddFromStorage(const RawAddress& addr,
                             const std::vector<uint8_t>& in);
  static bool GetForStorage(const RawAddress& addr, std::vector<uint8_t>& out);
  static void CleanUp(DeviceGroupsCallbacks* callbacks);
  static DeviceGroups* Get();
  /** To add to the existing group, group_id needs to be provided.
   *  Otherwise a new group for the given context uuid will be created.
   */
  virtual int AddDevice(
      const RawAddress& addr,
      bluetooth::Uuid uuid = bluetooth::groups::kGenericContextUuid,
      int group_id = bluetooth::groups::kGroupUnknown) = 0;
  virtual int GetGroupId(
      const RawAddress& addr,
      bluetooth::Uuid uuid = bluetooth::groups::kGenericContextUuid) const = 0;
  virtual void RemoveDevice(const RawAddress& addr) = 0;
};

}  // namespace groups
}  // namespace bluetooth
Loading