mixer.cc revision 58537e28ecd584eab876aee8be7156509866d23a
1// Copyright 2013 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/ui/app_list/search/mixer.h"
6
7#include <algorithm>
8#include <set>
9#include <string>
10#include <vector>
11
12#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
13#include "chrome/browser/ui/app_list/search/search_provider.h"
14
15namespace app_list {
16
17namespace {
18
19// Maximum number of results to show.
20const size_t kMaxResults = 6;
21const size_t kMaxMainGroupResults = 4;
22const size_t kMaxWebstoreResults = 2;
23const size_t kMaxPeopleResults = 2;
24
25// A value to indicate no max number of results limit.
26const size_t kNoMaxResultsLimit = 0;
27
28// Used for sorting and mixing results.
29struct SortData {
30  SortData()
31      : result(NULL),
32        score(0.0) {
33  }
34  SortData(ChromeSearchResult* result, double score)
35      : result(result),
36        score(score) {
37  }
38
39  bool operator<(const SortData& other) const {
40    // This data precedes (less than) |other| if it has higher score.
41    return score > other.score;
42  }
43
44  ChromeSearchResult* result;  // Not owned.
45  double score;
46};
47typedef std::vector<SortData> SortedResults;
48
49// Removes duplicates from |results|.
50void RemoveDuplicates(SortedResults* results) {
51  SortedResults final;
52  final.reserve(results->size());
53
54  std::set<std::string> id_set;
55  for (SortedResults::iterator it = results->begin();
56       it != results->end();
57       ++it) {
58    const std::string& id = it->result->id();
59    if (id_set.find(id) != id_set.end())
60      continue;
61
62    id_set.insert(id);
63    final.push_back(*it);
64  }
65
66  results->swap(final);
67}
68
69// Publishes the given |results| to |ui_results|. Reuse existing ones to avoid
70// flickering.
71void Publish(const SortedResults& results,
72             AppListModel::SearchResults* ui_results) {
73  for (size_t i = 0; i < results.size(); ++i) {
74    ChromeSearchResult* result = results[i].result;
75
76    ChromeSearchResult* ui_result = i < ui_results->item_count() ?
77        static_cast<ChromeSearchResult*>(ui_results->GetItemAt(i)) : NULL;
78    if (ui_result && ui_result->id() == result->id()) {
79      ui_result->set_title(result->title());
80      ui_result->set_title_tags(result->title_tags());
81      ui_result->set_details(result->details());
82      ui_result->set_details_tags(result->details_tags());
83      ui_results->NotifyItemsChanged(i, 1);
84    } else {
85      if (ui_result)
86        ui_results->DeleteAt(i);
87      ui_results->AddAt(i, result->Duplicate().release());
88    }
89  }
90
91  while (ui_results->item_count() > results.size())
92    ui_results->DeleteAt(ui_results->item_count() - 1);
93}
94
95}  // namespace
96
97// Used to group relevant providers together fox mixing their results.
98class Mixer::Group {
99 public:
100  Group(size_t max_results, double boost)
101      : max_results_(max_results),
102        boost_(boost) {
103  }
104  ~Group() {}
105
106  void AddProvider(SearchProvider* provider) {
107    providers_.push_back(provider);
108  }
109
110  void FetchResults(const KnownResults& known_results) {
111    results_.clear();
112
113    for (Providers::const_iterator provider_it = providers_.begin();
114         provider_it != providers_.end();
115         ++provider_it) {
116      for (SearchProvider::Results::const_iterator
117               result_it = (*provider_it)->results().begin();
118               result_it != (*provider_it)->results().end();
119               ++result_it) {
120        DCHECK_GE((*result_it)->relevance(), 0.0);
121        DCHECK_LE((*result_it)->relevance(), 1.0);
122        DCHECK(!(*result_it)->id().empty());
123
124        double boost = boost_;
125        KnownResults::const_iterator known_it =
126            known_results.find((*result_it)->id());
127        if (known_it != known_results.end()) {
128          switch (known_it->second) {
129            case PERFECT_PRIMARY:
130              boost = 4.0;
131              break;
132            case PREFIX_PRIMARY:
133              boost = 3.75;
134              break;
135            case PERFECT_SECONDARY:
136              boost = 3.25;
137              break;
138            case PREFIX_SECONDARY:
139              boost = 3.0;
140              break;
141            case UNKNOWN_RESULT:
142              NOTREACHED() << "Unknown result in KnownResults?";
143              break;
144          }
145        }
146
147        results_.push_back(
148            SortData(*result_it, (*result_it)->relevance() + boost));
149      }
150    }
151
152    std::sort(results_.begin(), results_.end());
153    if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_)
154      results_.resize(max_results_);
155  }
156
157  const SortedResults& results() const { return results_; }
158
159 private:
160  typedef std::vector<SearchProvider*> Providers;
161  const size_t max_results_;
162  const double boost_;
163
164  Providers providers_;  // Not owned.
165  SortedResults results_;
166
167  DISALLOW_COPY_AND_ASSIGN(Group);
168};
169
170Mixer::Mixer(AppListModel::SearchResults* ui_results)
171    : ui_results_(ui_results) {}
172Mixer::~Mixer() {}
173
174void Mixer::Init() {
175  groups_.push_back(new Group(kMaxMainGroupResults, 3.0));
176  groups_.push_back(new Group(kNoMaxResultsLimit, 2.0));
177  groups_.push_back(new Group(kMaxWebstoreResults, 1.0));
178  groups_.push_back(new Group(kMaxPeopleResults, 0.0));
179}
180
181void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) {
182  size_t group_index = static_cast<size_t>(group);
183  groups_[group_index]->AddProvider(provider);
184}
185
186void Mixer::MixAndPublish(const KnownResults& known_results) {
187  FetchResults(known_results);
188
189  SortedResults results;
190  results.reserve(kMaxResults);
191
192  // Adds main group and web store results first.
193  results.insert(results.end(),
194                 groups_[MAIN_GROUP]->results().begin(),
195                 groups_[MAIN_GROUP]->results().end());
196  results.insert(results.end(),
197                 groups_[WEBSTORE_GROUP]->results().begin(),
198                 groups_[WEBSTORE_GROUP]->results().end());
199  results.insert(results.end(),
200                 groups_[PEOPLE_GROUP]->results().begin(),
201                 groups_[PEOPLE_GROUP]->results().end());
202
203  // Collapse duplicate apps from local and web store.
204  RemoveDuplicates(&results);
205
206  DCHECK_GE(kMaxResults, results.size());
207  size_t remaining_slots = kMaxResults - results.size();
208
209  // Reserves at least one slot for the omnibox result. If there is no available
210  // slot for omnibox results, removes the last one from web store.
211  const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size();
212  if (!remaining_slots && omnibox_results)
213    results.pop_back();
214
215  remaining_slots = std::min(kMaxResults - results.size(), omnibox_results);
216  results.insert(results.end(),
217                 groups_[OMNIBOX_GROUP]->results().begin(),
218                 groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots);
219
220  std::sort(results.begin(), results.end());
221  RemoveDuplicates(&results);
222  if (results.size() > kMaxResults)
223    results.resize(kMaxResults);
224
225  Publish(results, ui_results_);
226}
227
228void Mixer::FetchResults(const KnownResults& known_results) {
229  for (Groups::iterator group_it = groups_.begin();
230       group_it != groups_.end();
231       ++group_it) {
232    (*group_it)->FetchResults(known_results);
233  }
234}
235
236}  // namespace app_list
237