1// Copyright (c) 2011 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 "chrome/browser/autocomplete/extension_app_provider.h"
6
7#include <algorithm>
8#include <cmath>
9
10#include "base/string16.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/history/history.h"
14#include "chrome/browser/history/url_database.h"
15#include "chrome/browser/profiles/profile.h"
16#include "content/common/notification_service.h"
17#include "ui/base/l10n/l10n_util.h"
18
19ExtensionAppProvider::ExtensionAppProvider(ACProviderListener* listener,
20                                           Profile* profile)
21    : AutocompleteProvider(listener, profile, "ExtensionApps") {
22  RegisterForNotifications();
23  RefreshAppList();
24}
25
26void ExtensionAppProvider::AddExtensionAppForTesting(
27    const std::string& app_name,
28    const std::string url) {
29  extension_apps_.push_back(std::make_pair(app_name, url));
30}
31
32void ExtensionAppProvider::Start(const AutocompleteInput& input,
33                                 bool minimal_changes) {
34  matches_.clear();
35
36  if (input.type() == AutocompleteInput::INVALID)
37    return;
38
39  if (!input.text().empty()) {
40    std::string input_utf8 = UTF16ToUTF8(input.text());
41    for (ExtensionApps::const_iterator app = extension_apps_.begin();
42         app != extension_apps_.end(); ++app) {
43      // See if the input matches this extension application.
44      const std::string& name = app->first;
45      const std::string& url = app->second;
46      std::string::const_iterator name_iter =
47          std::search(name.begin(),
48                      name.end(),
49                      input_utf8.begin(),
50                      input_utf8.end(),
51                      base::CaseInsensitiveCompare<char>());
52      std::string::const_iterator url_iter =
53          std::search(url.begin(),
54                      url.end(),
55                      input_utf8.begin(),
56                      input_utf8.end(),
57                      base::CaseInsensitiveCompare<char>());
58
59      bool matches_name = name_iter != name.end();
60      bool matches_url = url_iter != url.end() &&
61                         input.type() != AutocompleteInput::FORCED_QUERY;
62      if (matches_name || matches_url) {
63        // We have a match, might be a partial match.
64        // TODO(finnur): Figure out what type to return here, might want to have
65        // the extension icon/a generic icon show up in the Omnibox.
66        AutocompleteMatch match(this, 0, false,
67                                AutocompleteMatch::EXTENSION_APP);
68        match.fill_into_edit = UTF8ToUTF16(url);
69        match.destination_url = GURL(url);
70        match.inline_autocomplete_offset = string16::npos;
71        match.contents = UTF8ToUTF16(name);
72        HighlightMatch(input, &match.contents_class, name_iter, name);
73        match.description = UTF8ToUTF16(url);
74        HighlightMatch(input, &match.description_class, url_iter, url);
75        match.relevance = CalculateRelevance(input.type(),
76                                             input.text().length(),
77                                             matches_name ?
78                                                 name.length() : url.length(),
79                                             GURL(url));
80        matches_.push_back(match);
81      }
82    }
83  }
84}
85
86ExtensionAppProvider::~ExtensionAppProvider() {
87}
88
89void ExtensionAppProvider::RefreshAppList() {
90  ExtensionService* extension_service = profile_->GetExtensionService();
91  if (!extension_service)
92    return;  // During testing, there is no extension service.
93  const ExtensionList* extensions = extension_service->extensions();
94  extension_apps_.clear();
95  for (ExtensionList::const_iterator app = extensions->begin();
96       app != extensions->end(); ++app) {
97    if ((*app)->is_app() && (*app)->GetFullLaunchURL().is_valid()) {
98      extension_apps_.push_back(
99          std::make_pair((*app)->name(),
100                         (*app)->GetFullLaunchURL().spec()));
101    }
102  }
103}
104
105void ExtensionAppProvider::RegisterForNotifications() {
106  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
107                 NotificationService::AllSources());
108  registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
109                 NotificationService::AllSources());
110}
111
112void ExtensionAppProvider::Observe(NotificationType type,
113                                   const NotificationSource& source,
114                                   const NotificationDetails& details) {
115  RefreshAppList();
116}
117
118void ExtensionAppProvider::HighlightMatch(const AutocompleteInput& input,
119                                          ACMatchClassifications* match_class,
120                                          std::string::const_iterator iter,
121                                          const std::string& match_string) {
122  size_t pos = iter - match_string.begin();
123  bool match_found = iter != match_string.end();
124  if (!match_found || pos > 0) {
125    match_class->push_back(
126        ACMatchClassification(0, ACMatchClassification::DIM));
127  }
128  if (match_found) {
129    match_class->push_back(
130        ACMatchClassification(pos, ACMatchClassification::MATCH));
131    if (pos + input.text().length() < match_string.length()) {
132      match_class->push_back(ACMatchClassification(pos + input.text().length(),
133                                ACMatchClassification::DIM));
134    }
135  }
136}
137
138int ExtensionAppProvider::CalculateRelevance(AutocompleteInput::Type type,
139                                             int input_length,
140                                             int target_length,
141                                             const GURL& url) {
142  // If you update the algorithm here, please remember to update the tables in
143  // autocomplete.h also.
144  const int kMaxRelevance = 1425;
145
146  if (input_length == target_length)
147    return kMaxRelevance;
148
149  // We give a boost proportionally based on how much of the input matches the
150  // app name, up to a maximum close to 200 (we can be close to, but we'll never
151  // reach 200 because the 100% match is taken care of above).
152  double fraction_boost = static_cast<double>(200) *
153                          input_length / target_length;
154
155  // We also give a boost relative to how often the user has previously typed
156  // the Extension App URL/selected the Extension App suggestion from this
157  // provider (boost is between 200-400).
158  double type_count_boost = 0;
159  HistoryService* const history_service =
160      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
161  history::URLDatabase* url_db = history_service ?
162      history_service->InMemoryDatabase() : NULL;
163  if (url_db) {
164    history::URLRow info;
165    url_db->GetRowForURL(url, &info);
166    type_count_boost =
167        400 * (1.0 - (std::pow(static_cast<double>(2), -info.typed_count())));
168  }
169  int relevance = 575 + static_cast<int>(type_count_boost) +
170                        static_cast<int>(fraction_boost);
171  DCHECK_LE(relevance, kMaxRelevance);
172  return relevance;
173}
174