| Index: chrome/browser/autocomplete/contact_provider_chromeos.cc
|
| diff --git a/chrome/browser/autocomplete/contact_provider_chromeos.cc b/chrome/browser/autocomplete/contact_provider_chromeos.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0eb0c214c8b683674cf4877b340d2b7cb7777c5f
|
| --- /dev/null
|
| +++ b/chrome/browser/autocomplete/contact_provider_chromeos.cc
|
| @@ -0,0 +1,313 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/autocomplete/contact_provider_chromeos.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +#include "base/string_split.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "chrome/browser/autocomplete/autocomplete_input.h"
|
| +#include "chrome/browser/chromeos/contacts/contact.pb.h"
|
| +#include "chrome/browser/chromeos/contacts/contact_manager.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "unicode/usearch.h"
|
| +
|
| +namespace {
|
| +
|
| +// Searches for the first occurrence of |substring| within |string|. Case and
|
| +// accent differences are ignored -- see "primary level" at
|
| +// http://userguide.icu-project.org/collation/concepts.
|
| +//
|
| +// false is returned if |substring| is not found or if an error occurs. If
|
| +// non-NULL, |string_start_index| is set to the 0-based index where the match
|
| +// begins and |string_match_length| is set to the length of the matched text.
|
| +bool FindFirstSubstring(const string16& string,
|
| + const string16& substring,
|
| + size_t* string_start_index,
|
| + size_t* string_match_length) {
|
| + if (string.empty() || substring.empty())
|
| + return false;
|
| +
|
| + UErrorCode status = U_ZERO_ERROR;
|
| + UStringSearch* search = usearch_open(substring.data(), -1,
|
| + string.data(), -1,
|
| + uloc_getDefault(),
|
| + NULL, // breakiter
|
| + &status);
|
| + if (!U_SUCCESS(status))
|
| + return false;
|
| +
|
| + UCollator* collator = usearch_getCollator(search);
|
| + ucol_setStrength(collator, UCOL_PRIMARY);
|
| + usearch_reset(search);
|
| +
|
| + int32_t start_index = usearch_first(search, &status);
|
| + int32_t match_length = usearch_getMatchedLength(search);
|
| + usearch_close(search);
|
| +
|
| + if (!U_SUCCESS(status) || start_index == USEARCH_DONE)
|
| + return false;
|
| +
|
| + DCHECK_GE(start_index, 0);
|
| + DCHECK_GE(match_length, 0);
|
| + DCHECK_LE(start_index + match_length, static_cast<int32_t>(string.size()));
|
| +
|
| + if (string_start_index)
|
| + *string_start_index = static_cast<size_t>(start_index);
|
| + if (string_match_length)
|
| + *string_match_length = static_cast<size_t>(match_length);
|
| + return true;
|
| +}
|
| +
|
| +// Fills |classifications| based on |match_spans|, which consists of sorted
|
| +// [index, length] pairs for matching text. Overlapping or adjacent spans are
|
| +// collapsed where possible. Returns false on invalid input.
|
| +bool FillACMatchClassifications(
|
| + const ContactProvider::MatchSpans& match_spans,
|
| + size_t text_length,
|
| + AutocompleteMatch::ACMatchClassifications* classifications) {
|
| + DCHECK(classifications);
|
| + classifications->clear();
|
| +
|
| + // Index where the previous span started.
|
| + size_t last_span_start = 0;
|
| +
|
| + // One beyond the final character in the previous span (e.g. if there's a
|
| + // span at offset 0 with length 1, this will be set to 1).
|
| + size_t last_span_end = 0;
|
| +
|
| + for (ContactProvider::MatchSpans::const_iterator it = match_spans.begin();
|
| + it != match_spans.end(); ++it) {
|
| + size_t span_start = it->first;
|
| + size_t span_length = it->second;
|
| +
|
| + if (span_start < last_span_start || span_start + span_length > text_length)
|
| + return false;
|
| +
|
| + if (span_length == 0)
|
| + continue;
|
| +
|
| + if (span_start > last_span_end || last_span_end == 0) {
|
| + if (span_start != 0) {
|
| + // If we're not at the beginning of the string, add a preceding unstyled
|
| + // classification.
|
| + classifications->push_back(
|
| + ACMatchClassification(
|
| + last_span_end,
|
| + AutocompleteMatch::ACMatchClassification::NONE));
|
| + }
|
| + classifications->push_back(
|
| + ACMatchClassification(
|
| + span_start, AutocompleteMatch::ACMatchClassification::MATCH));
|
| + last_span_end = span_start + span_length;
|
| + } else {
|
| + // This span starts at or before the point where the last span ended, so
|
| + // don't bother adding an additional classification.
|
| + last_span_end = std::max(last_span_end, span_start + span_length);
|
| + }
|
| +
|
| + last_span_start = span_start;
|
| + }
|
| +
|
| + if (last_span_end < text_length) {
|
| + classifications->push_back(
|
| + ACMatchClassification(
|
| + last_span_end, AutocompleteMatch::ACMatchClassification::NONE));
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// static
|
| +const char ContactProvider::kMatchContactIdKey[] = "contact_id";
|
| +
|
| +// Cached information about a contact.
|
| +struct ContactProvider::ContactData {
|
| + ContactData(const string16& full_name,
|
| + const string16& given_name,
|
| + const string16& family_name,
|
| + const std::string& contact_id)
|
| + : full_name(full_name),
|
| + given_name(given_name),
|
| + family_name(family_name),
|
| + given_name_index(0),
|
| + family_name_index(0),
|
| + given_name_index_valid(false),
|
| + family_name_index_valid(false),
|
| + contact_id(contact_id) {
|
| + given_name_index_valid =
|
| + FindFirstSubstring(full_name, given_name, &given_name_index, NULL);
|
| + family_name_index_valid =
|
| + FindFirstSubstring(full_name, family_name, &family_name_index, NULL);
|
| + }
|
| +
|
| + string16 full_name;
|
| + string16 given_name;
|
| + string16 family_name;
|
| +
|
| + // Indices into |full_name| where |given_name| and |family_name| first appear.
|
| + // Only valid when the corresponding |*_index_valid| field is true.
|
| + size_t given_name_index;
|
| + size_t family_name_index;
|
| +
|
| + // Do |given_name| or |family_name| appear in |full_name|?
|
| + bool given_name_index_valid;
|
| + bool family_name_index_valid;
|
| +
|
| + // Unique ID used to look up additional contact information.
|
| + std::string contact_id;
|
| +};
|
| +
|
| +ContactProvider::TestAPI::TestAPI(ContactProvider* provider)
|
| + : provider_(provider) {
|
| +}
|
| +
|
| +ContactProvider::TestAPI::~TestAPI() {
|
| + provider_ = NULL;
|
| +}
|
| +
|
| +// static
|
| +AutocompleteMatch::ACMatchClassifications
|
| +ContactProvider::TestAPI::RunFillACMatchClassifications(
|
| + const MatchSpans& match_spans,
|
| + size_t text_length) {
|
| + ACMatchClassifications classifications;
|
| + if (FillACMatchClassifications(match_spans, text_length, &classifications))
|
| + return classifications;
|
| + else
|
| + return ACMatchClassifications();
|
| +}
|
| +
|
| +ContactProvider::ContactProvider(
|
| + AutocompleteProviderListener* listener,
|
| + Profile* profile,
|
| + base::WeakPtr<contacts::ContactManagerInterface> contact_manager)
|
| + : AutocompleteProvider(listener, profile, "Contacts"),
|
| + contact_manager_(contact_manager) {
|
| + contact_manager_->AddObserver(this, profile);
|
| + RefreshContacts();
|
| +}
|
| +
|
| +void ContactProvider::Start(const AutocompleteInput& input,
|
| + bool minimal_changes) {
|
| + matches_.clear();
|
| +
|
| + if (input.type() != AutocompleteInput::UNKNOWN &&
|
| + input.type() != AutocompleteInput::QUERY &&
|
| + input.type() != AutocompleteInput::FORCED_QUERY) {
|
| + return;
|
| + }
|
| +
|
| + std::vector<string16> input_parts;
|
| + base::SplitStringAlongWhitespace(input.text(), &input_parts);
|
| +
|
| + for (ContactDataVector::const_iterator it = contacts_.begin();
|
| + it != contacts_.end(); ++it) {
|
| + const ContactData& contact = *it;
|
| +
|
| + // [index, length] spans within |contact.full_name| that are matched by the
|
| + // input.
|
| + MatchSpans match_spans;
|
| +
|
| + bool is_matched = false;
|
| + size_t match_index = 0, match_length = 0;
|
| +
|
| + // First, check if the whole input string is a prefix of the full name.
|
| + if (FindFirstSubstring(
|
| + contact.full_name, input.text(), &match_index, &match_length) &&
|
| + match_index == 0) {
|
| + is_matched = true;
|
| + match_spans.push_back(std::make_pair(0, match_length));
|
| + } else {
|
| + // If not, check whether every search term is a prefix of the given name
|
| + // or the family name.
|
| + for (std::vector<string16>::const_iterator it = input_parts.begin();
|
| + it != input_parts.end(); ++it) {
|
| + if (FindFirstSubstring(
|
| + contact.given_name, *it, &match_index, &match_length) &&
|
| + match_index == 0) {
|
| + is_matched = true;
|
| + if (contact.given_name_index_valid) {
|
| + match_spans.push_back(
|
| + std::make_pair(contact.given_name_index, match_length));
|
| + }
|
| + } else if (
|
| + FindFirstSubstring(
|
| + contact.family_name, *it, &match_index, &match_length) &&
|
| + match_index == 0) {
|
| + is_matched = true;
|
| + if (contact.family_name_index_valid) {
|
| + match_spans.push_back(
|
| + std::make_pair(contact.family_name_index, match_length));
|
| + }
|
| + } else {
|
| + is_matched = false;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (is_matched) {
|
| + std::sort(match_spans.begin(), match_spans.end());
|
| + matches_.push_back(CreateAutocompleteMatch(input, contact, match_spans));
|
| + }
|
| + }
|
| +}
|
| +
|
| +void ContactProvider::OnContactsUpdated(Profile* profile) {
|
| + DCHECK_EQ(profile, profile_);
|
| + RefreshContacts();
|
| +}
|
| +
|
| +ContactProvider::~ContactProvider() {
|
| + // Like ContactProvider, ContactManager gets destroyed at profile destruction.
|
| + // Make sure that this class doesn't try to access ContactManager after
|
| + // ContactManager is gone.
|
| + if (contact_manager_.get())
|
| + contact_manager_->RemoveObserver(this, profile_);
|
| +}
|
| +
|
| +void ContactProvider::RefreshContacts() {
|
| + typedef std::vector<const contacts::Contact*> ContactPointers;
|
| + scoped_ptr<ContactPointers> contacts =
|
| + contact_manager_->GetAllContacts(profile_);
|
| +
|
| + contacts_.clear();
|
| + for (ContactPointers::const_iterator it = contacts->begin();
|
| + it != contacts->end(); ++it) {
|
| + const contacts::Contact& contact = **it;
|
| + string16 full_name =
|
| + AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.full_name()));
|
| + string16 given_name =
|
| + AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.given_name()));
|
| + string16 family_name =
|
| + AutocompleteMatch::SanitizeString(UTF8ToUTF16(contact.family_name()));
|
| +
|
| + if (!full_name.empty()) {
|
| + contacts_.push_back(
|
| + ContactData(
|
| + full_name, given_name, family_name, contact.contact_id()));
|
| + }
|
| + }
|
| +}
|
| +
|
| +AutocompleteMatch ContactProvider::CreateAutocompleteMatch(
|
| + const AutocompleteInput& input,
|
| + const ContactData& contact,
|
| + const MatchSpans& match_spans) {
|
| + AutocompleteMatch match(this, 0, false, AutocompleteMatch::CONTACT);
|
| + match.fill_into_edit = input.text();
|
| + match.inline_autocomplete_offset = string16::npos;
|
| + match.contents = contact.full_name;
|
| + match.relevance = 1000; // FIXME: update autocomplete_provider.h
|
| + match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id);
|
| + FillACMatchClassifications(
|
| + match_spans,
|
| + match.contents.length(),
|
| + &match.contents_class);
|
| + return match;
|
| +}
|
|
|