| Index: ui/app_list/search/mixer.cc
|
| diff --git a/ui/app_list/search/mixer.cc b/ui/app_list/search/mixer.cc
|
| index 51b61a638f4dd4d46f6a61c0ebecb5f330dcafbd..47bf8148fcc2ddc5a351a92131d2e3f7b4658704 100644
|
| --- a/ui/app_list/search/mixer.cc
|
| +++ b/ui/app_list/search/mixer.cc
|
| @@ -10,6 +10,7 @@
|
| #include <string>
|
| #include <vector>
|
|
|
| +#include "base/metrics/field_trial.h"
|
| #include "ui/app_list/search_provider.h"
|
| #include "ui/app_list/search_result.h"
|
|
|
| @@ -17,9 +18,19 @@ namespace app_list {
|
|
|
| namespace {
|
|
|
| -// Maximum number of results to show.
|
| +// Maximum number of results to show. Ignored if the AppListMixer field trial is
|
| +// "Blended".
|
| const size_t kMaxResults = 6;
|
|
|
| +// The minimum number of results to show, if the AppListMixer field trial is
|
| +// "Blended". If this quota is not reached, the per-group limitations are
|
| +// removed and we try again. (We may still not reach the minumum, but at least
|
| +// we tried.) Ignored if the field trial is off.
|
| +const size_t kMinBlendedResults = 6;
|
| +
|
| +const char kAppListMixerFieldTrialName[] = "AppListMixer";
|
| +const char kAppListMixerFieldTrialEnabled[] = "Blended";
|
| +
|
| void UpdateResult(const SearchResult& source, SearchResult* target) {
|
| target->set_display_type(source.display_type());
|
| target->set_title(source.title());
|
| @@ -28,6 +39,15 @@ void UpdateResult(const SearchResult& source, SearchResult* target) {
|
| target->set_details_tags(source.details_tags());
|
| }
|
|
|
| +// Returns true if the "AppListMixer" trial is set to "Blended". This is an
|
| +// experiment on the new Mixer logic that allows results from different groups
|
| +// to be blended together, rather than stratified.
|
| +bool IsBlendedMixerTrialEnabled() {
|
| + const std::string group_name =
|
| + base::FieldTrialList::FindFullName(kAppListMixerFieldTrialName);
|
| + return group_name == kAppListMixerFieldTrialEnabled;
|
| +}
|
| +
|
| } // namespace
|
|
|
| Mixer::SortData::SortData() : result(NULL), score(0.0) {
|
| @@ -45,8 +65,8 @@ bool Mixer::SortData::operator<(const SortData& other) const {
|
| // Used to group relevant providers together for mixing their results.
|
| class Mixer::Group {
|
| public:
|
| - Group(size_t max_results, double boost)
|
| - : max_results_(max_results), boost_(boost) {}
|
| + Group(size_t max_results, double boost, double multiplier)
|
| + : max_results_(max_results), boost_(boost), multiplier_(multiplier) {}
|
| ~Group() {}
|
|
|
| void AddProvider(SearchProvider* provider) { providers_.push_back(provider); }
|
| @@ -63,6 +83,7 @@ class Mixer::Group {
|
| // Google+ API). Clamp to that range.
|
| double relevance = std::min(std::max(result->relevance(), 0.0), 1.0);
|
|
|
| + double multiplier = multiplier_;
|
| double boost = boost_;
|
| KnownResults::const_iterator known_it =
|
| known_results.find(result->id());
|
| @@ -90,7 +111,7 @@ class Mixer::Group {
|
| if (is_voice_query && result->voice_result())
|
| boost += 4.0;
|
|
|
| - results_.push_back(SortData(result, relevance + boost));
|
| + results_.push_back(SortData(result, relevance * multiplier + boost));
|
| }
|
| }
|
|
|
| @@ -105,6 +126,7 @@ class Mixer::Group {
|
| typedef std::vector<SearchProvider*> Providers;
|
| const size_t max_results_;
|
| const double boost_;
|
| + const double multiplier_;
|
|
|
| Providers providers_; // Not owned.
|
| SortedResults results_;
|
| @@ -118,15 +140,23 @@ Mixer::Mixer(AppListModel::SearchResults* ui_results)
|
| Mixer::~Mixer() {
|
| }
|
|
|
| -size_t Mixer::AddGroup(size_t max_results, double boost) {
|
| - groups_.push_back(new Group(max_results, boost));
|
| +size_t Mixer::AddGroup(size_t max_results, double boost, double multiplier) {
|
| + // Only consider |boost| if the AppListMixer field trial is default.
|
| + // Only consider |multiplier| if the AppListMixer field trial is "Blended".
|
| + if (IsBlendedMixerTrialEnabled())
|
| + boost = 0.0;
|
| + else
|
| + multiplier = 1.0;
|
| + groups_.push_back(new Group(max_results, boost, multiplier));
|
| return groups_.size() - 1;
|
| }
|
|
|
| -size_t Mixer::AddOmniboxGroup(size_t max_results, double boost) {
|
| +size_t Mixer::AddOmniboxGroup(size_t max_results,
|
| + double boost,
|
| + double multiplier) {
|
| // There should not already be an omnibox group.
|
| DCHECK(!has_omnibox_group_);
|
| - size_t id = AddGroup(max_results, boost);
|
| + size_t id = AddGroup(max_results, boost, multiplier);
|
| omnibox_group_ = id;
|
| has_omnibox_group_ = true;
|
| return id;
|
| @@ -141,41 +171,79 @@ void Mixer::MixAndPublish(bool is_voice_query,
|
| FetchResults(is_voice_query, known_results);
|
|
|
| SortedResults results;
|
| - results.reserve(kMaxResults);
|
|
|
| - // Add results from non-omnibox groups first. Limit to the maximum number of
|
| - // results in each group.
|
| - for (size_t i = 0; i < groups_.size(); ++i) {
|
| - if (!has_omnibox_group_ || i != omnibox_group_) {
|
| - const Group& group = *groups_[i];
|
| + if (IsBlendedMixerTrialEnabled()) {
|
| + results.reserve(kMinBlendedResults);
|
| +
|
| + // Add results from each group. Limit to the maximum number of results in
|
| + // each group.
|
| + for (const Group* group : groups_) {
|
| size_t num_results =
|
| - std::min(group.results().size(), group.max_results());
|
| - results.insert(results.end(), group.results().begin(),
|
| - group.results().begin() + num_results);
|
| + std::min(group->results().size(), group->max_results());
|
| + results.insert(results.end(), group->results().begin(),
|
| + group->results().begin() + num_results);
|
| + }
|
| + // Remove results with duplicate IDs before sorting. If two providers give a
|
| + // result with the same ID, the result from the provider with the *lower
|
| + // group number* will be kept (e.g., an app result takes priority over a web
|
| + // store result with the same ID).
|
| + RemoveDuplicates(&results);
|
| + std::sort(results.begin(), results.end());
|
| +
|
| + if (results.size() < kMinBlendedResults) {
|
| + size_t original_size = results.size();
|
| + // We didn't get enough results. Insert all the results again, and this
|
| + // time, do not limit the maximum number of results from each group. (This
|
| + // will result in duplicates, which will be removed by RemoveDuplicates.)
|
| + for (const Group* group : groups_) {
|
| + results.insert(results.end(), group->results().begin(),
|
| + group->results().end());
|
| + }
|
| + RemoveDuplicates(&results);
|
| + // Sort just the newly added results. This ensures that, for example, if
|
| + // there are 6 Omnibox results (score = 0.8) and 1 People result (score =
|
| + // 0.4) that the People result will be 5th, not 7th, because the Omnibox
|
| + // group has a soft maximum of 4 results. (Otherwise, the People result
|
| + // would not be seen at all once the result list is truncated.)
|
| + std::sort(results.begin() + original_size, results.end());
|
| + }
|
| + } else {
|
| + results.reserve(kMaxResults);
|
| +
|
| + // Add results from non-omnibox groups first. Limit to the maximum number of
|
| + // results in each group.
|
| + for (size_t i = 0; i < groups_.size(); ++i) {
|
| + if (!has_omnibox_group_ || i != omnibox_group_) {
|
| + const Group& group = *groups_[i];
|
| + size_t num_results =
|
| + std::min(group.results().size(), group.max_results());
|
| + results.insert(results.end(), group.results().begin(),
|
| + group.results().begin() + num_results);
|
| + }
|
| }
|
| - }
|
|
|
| - // Collapse duplicate apps from local and web store.
|
| - RemoveDuplicates(&results);
|
| -
|
| - // Fill the remaining slots with omnibox results. Always add at least one
|
| - // omnibox result (even if there are no more slots; if we over-fill the
|
| - // vector, the web store and people results will be removed in a later step).
|
| - // Note: max_results() is ignored for the omnibox group.
|
| - if (has_omnibox_group_) {
|
| - CHECK_LT(omnibox_group_, groups_.size());
|
| - const Group& omnibox_group = *groups_[omnibox_group_];
|
| - const size_t omnibox_results = std::min(
|
| - omnibox_group.results().size(),
|
| - results.size() < kMaxResults ? kMaxResults - results.size() : 1);
|
| - results.insert(results.end(), omnibox_group.results().begin(),
|
| - omnibox_group.results().begin() + omnibox_results);
|
| - }
|
| + // Collapse duplicate apps from local and web store.
|
| + RemoveDuplicates(&results);
|
| +
|
| + // Fill the remaining slots with omnibox results. Always add at least one
|
| + // omnibox result (even if there are no more slots; if we over-fill the
|
| + // vector, the web store and people results will be removed in a later
|
| + // step). Note: max_results() is ignored for the omnibox group.
|
| + if (has_omnibox_group_) {
|
| + CHECK_LT(omnibox_group_, groups_.size());
|
| + const Group& omnibox_group = *groups_[omnibox_group_];
|
| + const size_t omnibox_results = std::min(
|
| + omnibox_group.results().size(),
|
| + results.size() < kMaxResults ? kMaxResults - results.size() : 1);
|
| + results.insert(results.end(), omnibox_group.results().begin(),
|
| + omnibox_group.results().begin() + omnibox_results);
|
| + }
|
|
|
| - std::sort(results.begin(), results.end());
|
| - RemoveDuplicates(&results);
|
| - if (results.size() > kMaxResults)
|
| - results.resize(kMaxResults);
|
| + std::sort(results.begin(), results.end());
|
| + RemoveDuplicates(&results);
|
| + if (results.size() > kMaxResults)
|
| + results.resize(kMaxResults);
|
| + }
|
|
|
| Publish(results, ui_results_);
|
| }
|
|
|