chromium/chrome/browser/nearby_sharing/contacts/nearby_share_contacts_sorter.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/nearby_sharing/contacts/nearby_share_contacts_sorter.h"

#include <algorithm>
#include <optional>
#include <string>

#include "base/i18n/string_compare.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "third_party/nearby/sharing/proto/rpc_resources.pb.h"

namespace {

struct ContactSortingFields {
  // Primary sorting key: person name if not empty; otherwise, email.
  std::optional<std::string> person_name_or_email;
  // Secondary sorting key. Note: It is okay if email is also used as the
  // primary sorting key.
  std::optional<std::string> email;
  // Tertiary sorting key.
  std::optional<std::string> phone_number;
  // Last resort sorting key. The contact ID should be unique for each contact
  // record, guaranteeing uniquely defined ordering.
  std::string id;
};

ContactSortingFields GetContactSortingFields(
    const nearby::sharing::proto::ContactRecord& contact) {
  ContactSortingFields fields;
  fields.id = contact.id();
  for (const auto& identifier : contact.identifiers()) {
    switch (identifier.identifier_case()) {
      case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
          kAccountName:
        if (!fields.email) {
          fields.email = identifier.account_name();
        }
        break;
      case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
          kPhoneNumber:
        if (!fields.phone_number) {
          fields.phone_number = identifier.phone_number();
        }
        break;
      case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
          kObfuscatedGaia:
        break;
      case nearby::sharing::proto::Contact_Identifier::IdentifierCase::
          IDENTIFIER_NOT_SET:
        break;
    }
  }
  fields.person_name_or_email =
      contact.person_name().empty()
          ? fields.email
          : std::make_optional<std::string>(contact.person_name());

  return fields;
}

class ContactRecordComparator {
 public:
  explicit ContactRecordComparator(icu::Collator* collator)
      : collator_(collator) {}

  bool operator()(const nearby::sharing::proto::ContactRecord& c1,
                  const nearby::sharing::proto::ContactRecord& c2) const {
    ContactSortingFields f1 = GetContactSortingFields(c1);
    ContactSortingFields f2 = GetContactSortingFields(c2);

    switch (CollatorCompare(f1.person_name_or_email, f2.person_name_or_email)) {
      case UCOL_EQUAL:
        // Do nothing. Compare by next field.
        break;
      case UCOL_LESS:
        return true;
      case UCOL_GREATER:
        return false;
    }

    switch (CollatorCompare(f1.email, f2.email)) {
      case UCOL_EQUAL:
        // Do nothing. Compare by next field.
        break;
      case UCOL_LESS:
        return true;
      case UCOL_GREATER:
        return false;
    }

    if (f1.phone_number != f2.phone_number) {
      if (!f1.phone_number)
        return false;
      if (!f2.phone_number)
        return true;
      return *f1.phone_number < *f2.phone_number;
    }

    return f1.id < f2.id;
  }

 private:
  UCollationResult CollatorCompare(const std::optional<std::string>& a,
                                   const std::optional<std::string>& b) const {
    // Sort populated strings before std::nullopt.
    if (!a && !b)
      return UCOL_EQUAL;
    if (!b)
      return UCOL_LESS;
    if (!a)
      return UCOL_GREATER;

    // Sort using a locale-based collator if available.
    if (collator_) {
      return base::i18n::CompareString16WithCollator(
          *collator_, base::UTF8ToUTF16(*a), base::UTF8ToUTF16(*b));
    }

    // Fall back on standard string comparison, though we hope and expect that
    // locale-based sorting will succeed.
    if (*a == *b) {
      return UCOL_EQUAL;
    }
    return *a < *b ? UCOL_LESS : UCOL_GREATER;
  }

  raw_ptr<icu::Collator> collator_;
};

}  // namespace

void SortNearbyShareContactRecords(
    std::vector<nearby::sharing::proto::ContactRecord>* contacts,
    icu::Locale locale) {
  UErrorCode error = U_ZERO_ERROR;
  std::unique_ptr<icu::Collator> collator(
      icu::Collator::createInstance(locale, error));
  ContactRecordComparator comparator(U_SUCCESS(error) ? collator.get()
                                                      : nullptr);
  std::sort(contacts->begin(), contacts->end(), comparator);
}