diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-12 03:32:06 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-12 03:32:06 +0000 |
commit | dbacefbdc2e432394a032098c511bcdac3ebde26 (patch) | |
tree | 27019f25204175c67aef4aeee13998f5dc1c854e | |
parent | d0436eeaed89a795db51ae63996fa551458b0d77 (diff) | |
download | chromium_src-dbacefbdc2e432394a032098c511bcdac3ebde26.zip chromium_src-dbacefbdc2e432394a032098c511bcdac3ebde26.tar.gz chromium_src-dbacefbdc2e432394a032098c511bcdac3ebde26.tar.bz2 |
autocomplete: Add ContactProvider.
This adds an AutocompleteProvider implementation that
searches through the user's contacts. It will eventually be
used to provide results in the Chrome OS app list.
ContactProvider built only on Chrome OS at the moment (as
that's also the only platform where the rest of the contacts
code is built) and is not instantiated outside of tests.
I'm also punting on ranking the results; all matches get a
relevance of 1.
BUG=141877
TEST=none
TBR=sky@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10907066
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@156224 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_controller.cc | 10 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_match.cc | 4 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_match.h | 1 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_provider.cc | 4 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_provider.h | 17 | ||||
-rw-r--r-- | chrome/browser/autocomplete/contact_provider_chromeos.cc | 207 | ||||
-rw-r--r-- | chrome/browser/autocomplete/contact_provider_chromeos.h | 74 | ||||
-rw-r--r-- | chrome/browser/autocomplete/contact_provider_chromeos_unittest.cc | 229 | ||||
-rw-r--r-- | chrome/browser/metrics/metrics_log.cc | 2 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/metrics/proto/omnibox_event.proto | 2 |
12 files changed, 545 insertions, 8 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_controller.cc b/chrome/browser/autocomplete/autocomplete_controller.cc index fc051a0..cc5d2b4 100644 --- a/chrome/browser/autocomplete/autocomplete_controller.cc +++ b/chrome/browser/autocomplete/autocomplete_controller.cc @@ -33,6 +33,11 @@ #include "grit/theme_resources.h" #include "ui/base/l10n/l10n_util.h" +#if defined(OS_CHROMEOS) +#include "chrome/browser/autocomplete/contact_provider_chromeos.h" +#include "chrome/browser/chromeos/contacts/contact_manager.h" +#endif + namespace { // Converts the given type to an integer based on the AQS specification. @@ -98,6 +103,11 @@ AutocompleteController::AutocompleteController( if (provider_types & AutocompleteProvider::TYPE_BUILTIN) providers_.push_back(new BuiltinProvider(this, profile)); +#if defined(OS_CHROMEOS) + if (provider_types & AutocompleteProvider::TYPE_CONTACT) + providers_.push_back(new ContactProvider(this, profile, + contacts::ContactManager::GetInstance()->GetWeakPtr())); +#endif if (provider_types & AutocompleteProvider::TYPE_EXTENSION_APP) providers_.push_back(new ExtensionAppProvider(this, profile)); if (provider_types & AutocompleteProvider::TYPE_HISTORY_CONTENTS) diff --git a/chrome/browser/autocomplete/autocomplete_match.cc b/chrome/browser/autocomplete/autocomplete_match.cc index f6d853e..9b4a7f7 100644 --- a/chrome/browser/autocomplete/autocomplete_match.cc +++ b/chrome/browser/autocomplete/autocomplete_match.cc @@ -143,6 +143,7 @@ std::string AutocompleteMatch::TypeToString(Type type) { "search-suggest", "search-other-engine", "extension-app", + "contact", }; COMPILE_ASSERT(arraysize(strings) == NUM_TYPES, strings_array_must_match_type_enum); @@ -163,6 +164,9 @@ int AutocompleteMatch::TypeToIcon(Type type) { IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_SEARCH, IDR_OMNIBOX_EXTENSION_APP, + // ContactProvider isn't used by the omnibox, so this icon is never + // displayed. + IDR_OMNIBOX_SEARCH, }; COMPILE_ASSERT(arraysize(icons) == NUM_TYPES, icons_array_must_match_type_enum); diff --git a/chrome/browser/autocomplete/autocomplete_match.h b/chrome/browser/autocomplete/autocomplete_match.h index ca32458..6dcbabb 100644 --- a/chrome/browser/autocomplete/autocomplete_match.h +++ b/chrome/browser/autocomplete/autocomplete_match.h @@ -89,6 +89,7 @@ struct AutocompleteMatch { SEARCH_OTHER_ENGINE, // A search with a non-default engine. EXTENSION_APP, // An Extension App with a title/url that contains // the input. + CONTACT, // One of the user's contacts. NUM_TYPES, }; diff --git a/chrome/browser/autocomplete/autocomplete_provider.cc b/chrome/browser/autocomplete/autocomplete_provider.cc index 58cd5b1b..2dde80d 100644 --- a/chrome/browser/autocomplete/autocomplete_provider.cc +++ b/chrome/browser/autocomplete/autocomplete_provider.cc @@ -36,6 +36,8 @@ const char* AutocompleteProvider::TypeToString(Type type) { switch (type) { case TYPE_BUILTIN: return "Builtin"; + case TYPE_CONTACT: + return "Contact"; case TYPE_EXTENSION_APP: return "ExtensionApp"; case TYPE_HISTORY_CONTENTS: @@ -71,6 +73,8 @@ metrics::OmniboxEventProto_ProviderType AutocompleteProvider:: switch (type_) { case TYPE_BUILTIN: return metrics::OmniboxEventProto::BUILTIN; + case TYPE_CONTACT: + return metrics::OmniboxEventProto::CONTACT; case TYPE_EXTENSION_APP: return metrics::OmniboxEventProto::EXTENSION_APPS; case TYPE_HISTORY_CONTENTS: diff --git a/chrome/browser/autocomplete/autocomplete_provider.h b/chrome/browser/autocomplete/autocomplete_provider.h index 377a5d3..9844a0f 100644 --- a/chrome/browser/autocomplete/autocomplete_provider.h +++ b/chrome/browser/autocomplete/autocomplete_provider.h @@ -176,14 +176,15 @@ class AutocompleteProvider // Different AutocompleteProvider implementations. enum Type { TYPE_BUILTIN = 1 << 0, - TYPE_EXTENSION_APP = 1 << 1, - TYPE_HISTORY_CONTENTS = 1 << 2, - TYPE_HISTORY_QUICK = 1 << 3, - TYPE_HISTORY_URL = 1 << 4, - TYPE_KEYWORD = 1 << 5, - TYPE_SEARCH = 1 << 6, - TYPE_SHORTCUTS = 1 << 7, - TYPE_ZERO_SUGGEST = 1 << 8, + TYPE_CONTACT = 1 << 1, + TYPE_EXTENSION_APP = 1 << 2, + TYPE_HISTORY_CONTENTS = 1 << 3, + TYPE_HISTORY_QUICK = 1 << 4, + TYPE_HISTORY_URL = 1 << 5, + TYPE_KEYWORD = 1 << 6, + TYPE_SEARCH = 1 << 7, + TYPE_SHORTCUTS = 1 << 8, + TYPE_ZERO_SUGGEST = 1 << 9, }; AutocompleteProvider(AutocompleteProviderListener* listener, diff --git a/chrome/browser/autocomplete/contact_provider_chromeos.cc b/chrome/browser/autocomplete/contact_provider_chromeos.cc new file mode 100644 index 0000000..2e7ff9a --- /dev/null +++ b/chrome/browser/autocomplete/contact_provider_chromeos.cc @@ -0,0 +1,207 @@ +// 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/i18n/break_iterator.h" +#include "base/i18n/string_search.h" +#include "base/string_split.h" +#include "base/string16.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" + +namespace { + +// Returns true if |word_to_find| is a prefix of |name_to_search| and marks the +// matching text in |classifications| (which corresponds to the contact's full +// name). |name_index_in_full_name| contains |name_to_search|'s index within +// the full name or string16::npos if it doesn't appear in it. +bool WordIsNamePrefix(const string16& word_to_find, + const string16& name_to_search, + size_t name_index_in_full_name, + size_t full_name_length, + ACMatchClassifications* classifications) { + DCHECK(classifications); + + size_t match_index = 0; + size_t match_length = 0; + if (!base::i18n::StringSearchIgnoringCaseAndAccents(word_to_find, + name_to_search, &match_index, &match_length) || (match_index != 0)) + return false; + + if (name_index_in_full_name != string16::npos) { + AutocompleteMatch::ACMatchClassifications new_class; + AutocompleteMatch::ClassifyLocationInString(name_index_in_full_name, + match_length, full_name_length, 0, &new_class); + *classifications = AutocompleteMatch::MergeClassifications( + *classifications, new_class); + } + + 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(string16::npos), + family_name_index(string16::npos), + contact_id(contact_id) { + base::i18n::StringSearchIgnoringCaseAndAccents( + given_name, full_name, &given_name_index, NULL); + base::i18n::StringSearchIgnoringCaseAndAccents( + family_name, full_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, + // or string16::npos if they don't appear in it. + size_t given_name_index; + size_t family_name_index; + + // Unique ID used to look up additional contact information. + std::string contact_id; +}; + +ContactProvider::ContactProvider( + AutocompleteProviderListener* listener, + Profile* profile, + base::WeakPtr<contacts::ContactManagerInterface> contact_manager) + : AutocompleteProvider(listener, profile, TYPE_CONTACT), + contact_manager_(contact_manager) { + contact_manager_->AddObserver(this, profile); + RefreshContacts(); +} + +void ContactProvider::Start(const AutocompleteInput& input, + bool minimal_changes) { + if (minimal_changes) + return; + + matches_.clear(); + + if (input.type() != AutocompleteInput::UNKNOWN && + input.type() != AutocompleteInput::QUERY && + input.type() != AutocompleteInput::FORCED_QUERY) + return; + + std::vector<string16> input_words; + base::i18n::BreakIterator break_iterator( + input.text(), + base::i18n::BreakIterator::BREAK_WORD); + if (break_iterator.Init()) { + while (break_iterator.Advance()) { + if (break_iterator.IsWord()) + input_words.push_back(break_iterator.GetString()); + } + } + + for (ContactDataVector::const_iterator it = contacts_.begin(); + it != contacts_.end(); ++it) + AddContactIfMatched(input, input_words, *it); +} + +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() { + if (!contact_manager_.get()) + return; + + scoped_ptr<contacts::ContactPointers> contacts = + contact_manager_->GetAllContacts(profile_); + + contacts_.clear(); + for (contacts::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())); + } + } +} + +void ContactProvider::AddContactIfMatched( + const AutocompleteInput& input, + const std::vector<string16>& input_words, + const ContactData& contact) { + // First, check if the whole input string is a prefix of the full name. + // TODO(derat): Consider additionally segmenting the full name so we can match + // e.g. middle names or initials even when they aren't typed as a prefix of + // the full name. + ACMatchClassifications classifications; + if (!WordIsNamePrefix(input.text(), contact.full_name, 0, + contact.full_name.size(), &classifications)) { + // If not, check whether every search term is a prefix of the given name + // or the family name. + if (input_words.empty()) + return; + + // TODO(derat): Check new matches against previous ones to make sure they + // don't overlap (e.g. the query "bob b" against a contact with full name + // "Bob G. Bryson", given name "Bob", and family name "Bryson" should result + // in classifications "_Bob_ G. _B_ryson" rather than "_Bob_ G. Bryson". + for (std::vector<string16>::const_iterator it = input_words.begin(); + it != input_words.end(); ++it) { + if (!WordIsNamePrefix(*it, contact.given_name, contact.given_name_index, + contact.full_name.size(), &classifications) && + !WordIsNamePrefix(*it, contact.family_name, contact.family_name_index, + contact.full_name.size(), &classifications)) + return; + } + } + + matches_.push_back(CreateAutocompleteMatch(input, contact)); + matches_.back().contents_class = classifications; +} + +AutocompleteMatch ContactProvider::CreateAutocompleteMatch( + const AutocompleteInput& input, + const ContactData& contact) { + AutocompleteMatch match(this, 0, false, AutocompleteMatch::CONTACT); + match.inline_autocomplete_offset = string16::npos; + match.contents = contact.full_name; + match.fill_into_edit = match.contents; + // TODO(derat): Implement ranking. + match.relevance = 1; + match.RecordAdditionalInfo(kMatchContactIdKey, contact.contact_id); + return match; +} diff --git a/chrome/browser/autocomplete/contact_provider_chromeos.h b/chrome/browser/autocomplete/contact_provider_chromeos.h new file mode 100644 index 0000000..9880aa0 --- /dev/null +++ b/chrome/browser/autocomplete/contact_provider_chromeos.h @@ -0,0 +1,74 @@ +// 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. + +#ifndef CHROME_BROWSER_AUTOCOMPLETE_CONTACT_PROVIDER_CHROMEOS_H_ +#define CHROME_BROWSER_AUTOCOMPLETE_CONTACT_PROVIDER_CHROMEOS_H_ + +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/autocomplete/autocomplete_match.h" +#include "chrome/browser/autocomplete/autocomplete_provider.h" +#include "chrome/browser/chromeos/contacts/contact_manager_observer.h" + +class AutocompleteInput; + +namespace contacts { +class ContactManagerInterface; +} + +// AutocompleteProvider implementation that searches through the contacts +// provided by contacts::ContactManager. +class ContactProvider : public AutocompleteProvider, + public contacts::ContactManagerObserver { + public: + // Key within AutocompleteMatch::additional_info where the corresponding + // contact's ID is stored. + static const char kMatchContactIdKey[]; + + ContactProvider( + AutocompleteProviderListener* listener, + Profile* profile, + base::WeakPtr<contacts::ContactManagerInterface> contact_manager); + + // AutocompleteProvider overrides: + virtual void Start(const AutocompleteInput& input, + bool minimal_changes) OVERRIDE; + + // contacts::ContactManagerObserver overrides: + virtual void OnContactsUpdated(Profile* profile) OVERRIDE; + + private: + struct ContactData; + typedef std::vector<ContactData> ContactDataVector; + + virtual ~ContactProvider(); + + // Updates |contacts_| to match the contacts currently reported by + // ContactManager. + void RefreshContacts(); + + // Adds an AutocompleteMatch object for |contact| to |matches_| if |contact| + // is matched by |input|. |input_words| is |input.text()| split on word + // boundaries. + void AddContactIfMatched(const AutocompleteInput& input, + const std::vector<string16>& input_words, + const ContactData& contact); + + // Returns an AutocompleteMatch object corresponding to the passed-in data. + AutocompleteMatch CreateAutocompleteMatch(const AutocompleteInput& input, + const ContactData& contact); + + base::WeakPtr<contacts::ContactManagerInterface> contact_manager_; + + // Contacts through which we search. + ContactDataVector contacts_; + + DISALLOW_COPY_AND_ASSIGN(ContactProvider); +}; + +#endif // CHROME_BROWSER_AUTOCOMPLETE_CONTACT_PROVIDER_CHROMEOS_H_ diff --git a/chrome/browser/autocomplete/contact_provider_chromeos_unittest.cc b/chrome/browser/autocomplete/contact_provider_chromeos_unittest.cc new file mode 100644 index 0000000..52700a2 --- /dev/null +++ b/chrome/browser/autocomplete/contact_provider_chromeos_unittest.cc @@ -0,0 +1,229 @@ +// 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 <map> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete_input.h" +#include "chrome/browser/autocomplete/autocomplete_match.h" +#include "chrome/browser/autocomplete/autocomplete_provider.h" +#include "chrome/browser/chromeos/contacts/contact.pb.h" +#include "chrome/browser/chromeos/contacts/contact_manager_stub.h" +#include "chrome/browser/chromeos/contacts/contact_test_util.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +namespace { + +// Initializes |contact| with the passed-in data. +void InitContact(const std::string& contact_id, + const std::string& full_name, + const std::string& given_name, + const std::string& family_name, + contacts::Contact* contact) { + contact->set_contact_id(contact_id); + contact->set_full_name(full_name); + contact->set_given_name(given_name); + contact->set_family_name(family_name); +} + +} // namespace + +class ContactProviderTest : public testing::Test { + public: + ContactProviderTest() : ui_thread_(BrowserThread::UI, &message_loop_) {} + virtual ~ContactProviderTest() {} + + protected: + // testing::Test implementation. + virtual void SetUp() OVERRIDE { + profile_manager_.reset( + new TestingProfileManager( + static_cast<TestingBrowserProcess*>(g_browser_process))); + ASSERT_TRUE(profile_manager_->SetUp()); + profile_ = profile_manager_->CreateTestingProfile("test_profile"); + contact_manager_.reset(new contacts::ContactManagerStub(profile_)); + contact_provider_ = + new ContactProvider(NULL, profile_, contact_manager_->GetWeakPtr()); + } + + // Starts a (synchronous) query for |utf8_text| in |contact_provider_|. + void StartQuery(const std::string& utf8_text) { + contact_provider_->Start( + AutocompleteInput(UTF8ToUTF16(utf8_text), + string16(), // desired_tld + false, // prevent_inline_autocomplete + false, // prefer_keyword + false, // allow_exact_keyword_match + AutocompleteInput::ALL_MATCHES), + false); // minimal_changes + } + + // Returns pointers to all of the Contact objects referenced in + // |contact_provider_|'s current results. + contacts::ContactPointers GetMatchedContacts() { + contacts::ContactPointers contacts; + const ACMatches& matches = contact_provider_->matches(); + for (size_t i = 0; i < matches.size(); ++i) { + std::map<std::string, std::string>::const_iterator id_it = + matches[i].additional_info.find(ContactProvider::kMatchContactIdKey); + CHECK(id_it != matches[i].additional_info.end()); + const contacts::Contact* contact = + contact_manager_->GetContactById(profile_, id_it->second); + CHECK(contact) << "Unable to find contact with ID " << id_it->second; + contacts.push_back(contact); + } + return contacts; + } + + // Returns a semicolon-separated string containing string representations (as + // provided by AutocompleteMatch::ClassificationsToString()) of the + // |contents_class| fields of all current matches. Results are sorted by + // contact ID. + std::string GetMatchClassifications() { + typedef std::map<std::string, std::string> StringMap; + StringMap contact_id_classifications; + const ACMatches& matches = contact_provider_->matches(); + for (size_t i = 0; i < matches.size(); ++i) { + std::map<std::string, std::string>::const_iterator id_it = + matches[i].additional_info.find(ContactProvider::kMatchContactIdKey); + CHECK(id_it != matches[i].additional_info.end()); + contact_id_classifications[id_it->second] = + AutocompleteMatch::ClassificationsToString(matches[i].contents_class); + } + + std::string result; + for (StringMap::const_iterator it = contact_id_classifications.begin(); + it != contact_id_classifications.end(); ++it) { + if (!result.empty()) + result += ";"; + result += it->second; + } + return result; + } + + MessageLoopForUI message_loop_; + content::TestBrowserThread ui_thread_; + + scoped_ptr<TestingProfileManager> profile_manager_; + TestingProfile* profile_; + + scoped_ptr<contacts::ContactManagerStub> contact_manager_; + scoped_refptr<ContactProvider> contact_provider_; +}; + +TEST_F(ContactProviderTest, BasicMatching) { + const std::string kContactId1 = "contact_1"; + scoped_ptr<contacts::Contact> contact1(new contacts::Contact); + InitContact(kContactId1, "Bob Smith", "Bob", "Smith", contact1.get()); + + const std::string kContactId2 = "contact_2"; + scoped_ptr<contacts::Contact> contact2(new contacts::Contact); + InitContact(kContactId2, "Dr. Jane Smith", "Jane", "Smith", contact2.get()); + + contacts::ContactPointers contacts; + contacts.push_back(contact1.get()); + contacts.push_back(contact2.get()); + contact_manager_->SetContacts(contacts); + contact_manager_->NotifyObserversAboutUpdatedContacts(); + + StartQuery("b"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact1.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,1,0", GetMatchClassifications()); + + StartQuery("bob"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact1.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,3,0", GetMatchClassifications()); + + StartQuery("bob smith"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact1.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2", GetMatchClassifications()); + + StartQuery("sm"); + EXPECT_EQ( + contacts::test::VarContactsToString(2, contact1.get(), contact2.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,0,4,2,6,0;" "0,0,9,2,11,0", GetMatchClassifications()); + + StartQuery("smith"); + EXPECT_EQ( + contacts::test::VarContactsToString(2, contact1.get(), contact2.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,0,4,2;" "0,0,9,2", GetMatchClassifications()); + + StartQuery("smIth BOb"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact1.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,3,0,4,2", GetMatchClassifications()); + + StartQuery("bobo"); + EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("", GetMatchClassifications()); + + StartQuery("mith"); + EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("", GetMatchClassifications()); + + StartQuery("dr"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact2.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,2,0", GetMatchClassifications()); + + StartQuery("dr. j"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact2.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,5,0", GetMatchClassifications()); + + StartQuery("jane"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact2.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,0,4,2,8,0", GetMatchClassifications()); +} + +TEST_F(ContactProviderTest, Collation) { + scoped_ptr<contacts::Contact> contact(new contacts::Contact); + InitContact("1", "Bj\xC3\xB6rn Adelsv\xC3\xA4rd", + "Bj\xC3\xB6rn", "Adelsv\xC3\xA4rd", + contact.get()); + + contacts::ContactPointers contacts; + contacts.push_back(contact.get()); + contact_manager_->SetContacts(contacts); + contact_manager_->NotifyObserversAboutUpdatedContacts(); + + StartQuery("bjorn"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,2,5,0", GetMatchClassifications()); + + StartQuery("adelsvard"); + EXPECT_EQ( + contacts::test::VarContactsToString(1, contact.get()), + contacts::test::ContactsToString(GetMatchedContacts())); + EXPECT_EQ("0,0,6,2", GetMatchClassifications()); +} diff --git a/chrome/browser/metrics/metrics_log.cc b/chrome/browser/metrics/metrics_log.cc index e02e235..f88383b 100644 --- a/chrome/browser/metrics/metrics_log.cc +++ b/chrome/browser/metrics/metrics_log.cc @@ -121,6 +121,8 @@ OmniboxEventProto::Suggestion::ResultType AsOmniboxEventResultType( return OmniboxEventProto::Suggestion::SEARCH_OTHER_ENGINE; case AutocompleteMatch::EXTENSION_APP: return OmniboxEventProto::Suggestion::EXTENSION_APP; + case AutocompleteMatch::CONTACT: + return OmniboxEventProto::Suggestion::CONTACT; default: NOTREACHED(); return OmniboxEventProto::Suggestion::UNKNOWN_RESULT_TYPE; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 33f0866..ea0dcef 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -172,6 +172,8 @@ 'browser/autocomplete/autocomplete_result.h', 'browser/autocomplete/builtin_provider.cc', 'browser/autocomplete/builtin_provider.h', + 'browser/autocomplete/contact_provider_chromeos.cc', + 'browser/autocomplete/contact_provider_chromeos.h', 'browser/autocomplete/extension_app_provider.cc', 'browser/autocomplete/extension_app_provider.h', 'browser/autocomplete/history_contents_provider.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index ec55a9a..f77ad9a 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1009,6 +1009,7 @@ 'browser/autocomplete/autocomplete_provider_unittest.cc', 'browser/autocomplete/autocomplete_result_unittest.cc', 'browser/autocomplete/builtin_provider_unittest.cc', + 'browser/autocomplete/contact_provider_chromeos_unittest.cc', 'browser/autocomplete/extension_app_provider_unittest.cc', 'browser/autocomplete/history_contents_provider_unittest.cc', 'browser/autocomplete/history_quick_provider_unittest.cc', diff --git a/chrome/common/metrics/proto/omnibox_event.proto b/chrome/common/metrics/proto/omnibox_event.proto index 2c2bc99..60e5590 100644 --- a/chrome/common/metrics/proto/omnibox_event.proto +++ b/chrome/common/metrics/proto/omnibox_event.proto @@ -85,6 +85,7 @@ message OmniboxEventProto { BUILTIN = 6; // Built-in URLs, such as chrome://version SHORTCUTS = 7; // Recently selected omnibox suggestions EXTENSION_APPS = 8; // Custom suggestions from extensions and/or apps + CONTACT = 9; // The user's contacts } // The result set displayed on the completion popup @@ -114,6 +115,7 @@ message OmniboxEventProto { SEARCH_OTHER_ENGINE = 10; // A search with a non-default engine EXTENSION_APP = 11; // An Extension App with a title/url that // contains the input + CONTACT = 12; // One of the user's contacts }; optional ResultType result_type = 2; |