1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "athena/main/url_search_provider.h"
6
7#include "athena/activity/public/activity.h"
8#include "athena/activity/public/activity_factory.h"
9#include "athena/content/public/scheme_classifier_factory.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/values.h"
12#include "components/metrics/proto/omnibox_event.pb.h"
13#include "components/metrics/proto/omnibox_input_type.pb.h"
14#include "components/omnibox/autocomplete_input.h"
15#include "components/omnibox/autocomplete_provider_client.h"
16#include "components/omnibox/search_provider.h"
17#include "components/search_engines/search_terms_data.h"
18#include "components/search_engines/template_url_service.h"
19#include "components/search_engines/template_url_service_client.h"
20#include "content/public/browser/browser_context.h"
21#include "ui/app_list/search_result.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "url/gurl.h"
24
25namespace athena {
26
27namespace {
28
29// This constant was copied from HistoryURLProvider.
30// TODO(hashimoto): Componentize HistoryURLProvider and delete this.
31const int kScoreForWhatYouTypedResult = 1203;
32
33// The SearchTermsData implementation for Athena.
34class AthenaSearchTermsData : public SearchTermsData {
35 public:
36  // SearchTermsData:
37  virtual std::string GetSuggestClient() const OVERRIDE {
38    return "chrome";
39  }
40};
41
42// The templateURLServiceClient for Athena. Mainly for the interaction with
43// history module (see chrome/browser/search_engines for Chrome implementation).
44// TODO(mukai): Implement the contents of this class when it's necessary.
45class AthenaTemplateURLServiceClient : public TemplateURLServiceClient {
46 public:
47  AthenaTemplateURLServiceClient() {}
48  virtual ~AthenaTemplateURLServiceClient() {}
49
50 private:
51  // TemplateURLServiceClient:
52  virtual void SetOwner(TemplateURLService* owner) OVERRIDE {}
53  virtual void DeleteAllSearchTermsForKeyword(TemplateURLID id) OVERRIDE {}
54  virtual void SetKeywordSearchTermsForURL(
55      const GURL& url,
56      TemplateURLID id,
57      const base::string16& term) OVERRIDE {}
58  virtual void AddKeywordGeneratedVisit(const GURL& url) OVERRIDE {}
59  virtual void RestoreExtensionInfoIfNecessary(
60      TemplateURL* template_url) OVERRIDE {}
61
62  DISALLOW_COPY_AND_ASSIGN(AthenaTemplateURLServiceClient);
63};
64
65// The AutocompleteProviderClient for Athena.
66class AthenaAutocompleteProviderClient : public AutocompleteProviderClient {
67 public:
68  explicit AthenaAutocompleteProviderClient(
69      content::BrowserContext* browser_context)
70      : browser_context_(browser_context),
71        scheme_classifier_(CreateSchemeClassifier(browser_context)) {}
72  virtual ~AthenaAutocompleteProviderClient() {}
73
74  virtual net::URLRequestContextGetter* RequestContext() OVERRIDE {
75    return browser_context_->GetRequestContext();
76  }
77  virtual bool IsOffTheRecord() OVERRIDE {
78    return browser_context_->IsOffTheRecord();
79  }
80  virtual std::string AcceptLanguages() OVERRIDE {
81    // TODO(hashimoto): Return the value stored in the prefs.
82    return "en-US";
83  }
84  virtual bool SearchSuggestEnabled() OVERRIDE { return true; }
85  virtual bool ShowBookmarkBar() OVERRIDE { return false; }
86  virtual const AutocompleteSchemeClassifier& SchemeClassifier() OVERRIDE {
87    return *scheme_classifier_;
88  }
89  virtual void Classify(
90      const base::string16& text,
91      bool prefer_keyword,
92      bool allow_exact_keyword_match,
93      metrics::OmniboxEventProto::PageClassification page_classification,
94      AutocompleteMatch* match,
95      GURL* alternate_nav_url) OVERRIDE {}
96  virtual history::URLDatabase* InMemoryDatabase() OVERRIDE { return NULL; }
97  virtual void DeleteMatchingURLsForKeywordFromHistory(
98      history::KeywordID keyword_id,
99      const base::string16& term) OVERRIDE {}
100  virtual bool TabSyncEnabledAndUnencrypted() OVERRIDE { return false; }
101  virtual void PrefetchImage(const GURL& url) OVERRIDE {}
102
103 private:
104  content::BrowserContext* browser_context_;
105  scoped_ptr<AutocompleteSchemeClassifier> scheme_classifier_;
106
107  DISALLOW_COPY_AND_ASSIGN(AthenaAutocompleteProviderClient);
108};
109
110int ACMatchStyleToTagStyle(int styles) {
111  int tag_styles = 0;
112  if (styles & ACMatchClassification::URL)
113    tag_styles |= app_list::SearchResult::Tag::URL;
114  if (styles & ACMatchClassification::MATCH)
115    tag_styles |= app_list::SearchResult::Tag::MATCH;
116  if (styles & ACMatchClassification::DIM)
117    tag_styles |= app_list::SearchResult::Tag::DIM;
118
119  return tag_styles;
120}
121
122// Translates ACMatchClassifications into SearchResult tags.
123void ACMatchClassificationsToTags(
124    const base::string16& text,
125    const ACMatchClassifications& text_classes,
126    app_list::SearchResult::Tags* tags) {
127  int tag_styles = app_list::SearchResult::Tag::NONE;
128  size_t tag_start = 0;
129
130  for (size_t i = 0; i < text_classes.size(); ++i) {
131    const ACMatchClassification& text_class = text_classes[i];
132
133    // Closes current tag.
134    if (tag_styles != app_list::SearchResult::Tag::NONE) {
135      tags->push_back(app_list::SearchResult::Tag(
136          tag_styles, tag_start, text_class.offset));
137      tag_styles = app_list::SearchResult::Tag::NONE;
138    }
139
140    if (text_class.style == ACMatchClassification::NONE)
141      continue;
142
143    tag_start = text_class.offset;
144    tag_styles = ACMatchStyleToTagStyle(text_class.style);
145  }
146
147  if (tag_styles != app_list::SearchResult::Tag::NONE) {
148    tags->push_back(app_list::SearchResult::Tag(
149        tag_styles, tag_start, text.length()));
150  }
151}
152
153class UrlSearchResult : public app_list::SearchResult {
154 public:
155  UrlSearchResult(content::BrowserContext* browser_context,
156                  const AutocompleteMatch& match)
157      : browser_context_(browser_context),
158        match_(match) {
159    set_id(match_.destination_url.spec());
160
161    // Derive relevance from omnibox relevance and normalize it to [0, 1].
162    // The magic number 1500 is the highest score of an omnibox result.
163    // See comments in autocomplete_provider.h.
164    set_relevance(match_.relevance / 1500.0);
165
166    UpdateIcon();
167    UpdateTitleAndDetails();
168  }
169
170  virtual ~UrlSearchResult() {}
171
172 private:
173  // Overriddenn from app_list::SearchResult:
174  virtual void Open(int event_flags) OVERRIDE {
175    Activity* activity = ActivityFactory::Get()->CreateWebActivity(
176        browser_context_, base::string16(), match_.destination_url);
177    Activity::Show(activity);
178  }
179
180  void UpdateIcon() {
181    SetIcon(*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
182        AutocompleteMatch::TypeToIcon(match_.type)));
183  }
184
185  void UpdateTitleAndDetails() {
186    set_title(match_.contents);
187    SearchResult::Tags title_tags;
188    ACMatchClassificationsToTags(match_.contents,
189                                 match_.contents_class,
190                                 &title_tags);
191    set_title_tags(title_tags);
192
193    set_details(match_.description);
194    SearchResult::Tags details_tags;
195    ACMatchClassificationsToTags(match_.description,
196                                 match_.description_class,
197                                 &details_tags);
198    set_details_tags(details_tags);
199  }
200
201  content::BrowserContext* browser_context_;
202  AutocompleteMatch match_;
203
204  DISALLOW_COPY_AND_ASSIGN(UrlSearchResult);
205};
206
207}  // namespace
208
209UrlSearchProvider::UrlSearchProvider(content::BrowserContext* browser_context)
210    : browser_context_(browser_context),
211      // TODO(mukai): introduce the real parameters when it's necessary.
212      template_url_service_(
213          new TemplateURLService(NULL /* prefs */,
214                                 scoped_ptr<SearchTermsData>(
215                                     new AthenaSearchTermsData()),
216                                 NULL /* KeywordWebDataService */,
217                                 scoped_ptr<TemplateURLServiceClient>(
218                                     new AthenaTemplateURLServiceClient()),
219                                 NULL /*GoogleURLTracker */,
220                                 NULL /* RapporService */,
221                                 base::Closure() /* dsp_change_callback */)),
222      provider_(new ::SearchProvider(
223          this,
224          template_url_service_.get(),
225          scoped_ptr<AutocompleteProviderClient>(
226              new AthenaAutocompleteProviderClient(browser_context_)))) {
227  template_url_service_->Load();
228}
229
230UrlSearchProvider::~UrlSearchProvider() {
231}
232
233void UrlSearchProvider::Start(const base::string16& query) {
234  const bool minimal_changes = query == input_.text();
235  scoped_ptr<AutocompleteSchemeClassifier> scheme_classifier(
236      CreateSchemeClassifier(browser_context_));
237  input_ = AutocompleteInput(query,
238                             base::string16::npos /* cursor_position */,
239                             base::string16() /* desired_tld */,
240                             GURL() /* current_url */,
241                             metrics::OmniboxEventProto::INVALID_SPEC,
242                             false /* prevent_inline_autocomplete */,
243                             false /* prefer_keyword */,
244                             true /* allow_extract_keyword_match */,
245                             true /* want_asynchronous_matches */,
246                             *scheme_classifier);
247
248  // Clearing results here may cause unexpected results.
249  // TODO(mukai): fix this by fixing crbug.com/415500
250  if (!minimal_changes)
251    ClearResults();
252
253  if (input_.type() == metrics::OmniboxInputType::URL) {
254    // TODO(hashimoto): Componentize HistoryURLProvider and remove this code.
255    AutocompleteMatch what_you_typed_match(
256        NULL, 0, false, AutocompleteMatchType::URL_WHAT_YOU_TYPED);
257    what_you_typed_match.destination_url = input_.canonicalized_url();
258    what_you_typed_match.contents = input_.text();
259    what_you_typed_match.relevance = kScoreForWhatYouTypedResult;
260    Add(scoped_ptr<app_list::SearchResult>(new UrlSearchResult(
261        browser_context_, what_you_typed_match)));
262  }
263
264  provider_->Start(input_, minimal_changes);
265}
266
267void UrlSearchProvider::Stop() {
268  provider_->Stop(false);
269}
270
271void UrlSearchProvider::OnProviderUpdate(bool updated_matches) {
272  if (!updated_matches)
273    return;
274
275  const ACMatches& matches = provider_->matches();
276  for (ACMatches::const_iterator it = matches.begin(); it != matches.end();
277       ++it) {
278    if (!it->destination_url.is_valid())
279      continue;
280
281    Add(scoped_ptr<app_list::SearchResult>(new UrlSearchResult(
282        browser_context_, *it)));
283  }
284}
285
286}  // namespace athena
287