mixer.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 "ui/app_list/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(static_cast<ChromeSearchResult*>(*result_it),
149                     (*result_it)->relevance() + boost));
150      }
151    }
152
153    std::sort(results_.begin(), results_.end());
154    if (max_results_ != kNoMaxResultsLimit && results_.size() > max_results_)
155      results_.resize(max_results_);
156  }
157
158  const SortedResults& results() const { return results_; }
159
160 private:
161  typedef std::vector<SearchProvider*> Providers;
162  const size_t max_results_;
163  const double boost_;
164
165  Providers providers_;  // Not owned.
166  SortedResults results_;
167
168  DISALLOW_COPY_AND_ASSIGN(Group);
169};
170
171Mixer::Mixer(AppListModel::SearchResults* ui_results)
172    : ui_results_(ui_results) {}
173Mixer::~Mixer() {}
174
175void Mixer::Init() {
176  groups_.push_back(new Group(kMaxMainGroupResults, 3.0));
177  groups_.push_back(new Group(kNoMaxResultsLimit, 2.0));
178  groups_.push_back(new Group(kMaxWebstoreResults, 1.0));
179  groups_.push_back(new Group(kMaxPeopleResults, 0.0));
180}
181
182void Mixer::AddProviderToGroup(GroupId group, SearchProvider* provider) {
183  size_t group_index = static_cast<size_t>(group);
184  groups_[group_index]->AddProvider(provider);
185}
186
187void Mixer::MixAndPublish(const KnownResults& known_results) {
188  FetchResults(known_results);
189
190  SortedResults results;
191  results.reserve(kMaxResults);
192
193  // Adds main group and web store results first.
194  results.insert(results.end(),
195                 groups_[MAIN_GROUP]->results().begin(),
196                 groups_[MAIN_GROUP]->results().end());
197  results.insert(results.end(),
198                 groups_[WEBSTORE_GROUP]->results().begin(),
199                 groups_[WEBSTORE_GROUP]->results().end());
200  results.insert(results.end(),
201                 groups_[PEOPLE_GROUP]->results().begin(),
202                 groups_[PEOPLE_GROUP]->results().end());
203
204  // Collapse duplicate apps from local and web store.
205  RemoveDuplicates(&results);
206
207  DCHECK_GE(kMaxResults, results.size());
208  size_t remaining_slots = kMaxResults - results.size();
209
210  // Reserves at least one slot for the omnibox result. If there is no available
211  // slot for omnibox results, removes the last one from web store.
212  const size_t omnibox_results = groups_[OMNIBOX_GROUP]->results().size();
213  if (!remaining_slots && omnibox_results)
214    results.pop_back();
215
216  remaining_slots = std::min(kMaxResults - results.size(), omnibox_results);
217  results.insert(results.end(),
218                 groups_[OMNIBOX_GROUP]->results().begin(),
219                 groups_[OMNIBOX_GROUP]->results().begin() + remaining_slots);
220
221  std::sort(results.begin(), results.end());
222  RemoveDuplicates(&results);
223  if (results.size() > kMaxResults)
224    results.resize(kMaxResults);
225
226  Publish(results, ui_results_);
227}
228
229void Mixer::FetchResults(const KnownResults& known_results) {
230  for (Groups::iterator group_it = groups_.begin();
231       group_it != groups_.end();
232       ++group_it) {
233    (*group_it)->FetchResults(known_results);
234  }
235}
236
237}  // namespace app_list
238