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; |
+} |