chromium/chrome/browser/ash/app_list/search/omnibox/omnibox_provider.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h"

#include <iterator>
#include <string>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/ash/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ash/app_list/search/omnibox/omnibox_answer_result.h"
#include "chrome/browser/ash/app_list/search/omnibox/omnibox_result.h"
#include "chrome/browser/ash/app_list/search/omnibox/omnibox_util.h"
#include "chrome/browser/ash/app_list/search/omnibox/open_tab_result.h"
#include "chrome/browser/ash/app_list/search/search_provider.h"
#include "chrome/browser/ash/app_list/search/types.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_provider_client.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/chromeos/launcher_search/search_util.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/favicon/core/favicon_service.h"
#include "components/omnibox/browser/autocomplete_classifier.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/prefs/pref_service.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "third_party/metrics_proto/omnibox_focus_type.pb.h"
#include "third_party/omnibox_proto/answer_type.pb.h"
#include "url/gurl.h"

namespace app_list {

namespace {

using ::ash::string_matching::TokenizedString;

// Returns true if the match is an answer, including calculator answers.
bool IsAnswer(const AutocompleteMatch& match) {
  return match.answer_type != omnibox::ANSWER_TYPE_UNSPECIFIED ||
         match.type == AutocompleteMatchType::CALCULATOR;
}

}  //  namespace

// Control category is kept default intentionally as we always need to get
// answer cards results from Omnibox.
OmniboxProvider::OmniboxProvider(Profile* profile,
                                 AppListControllerDelegate* list_controller,
                                 int provider_types)
    : SearchProvider(SearchCategory::kOmnibox),
      profile_(profile),
      list_controller_(list_controller),
      favicon_cache_(FaviconServiceFactory::GetForProfile(
                         profile,
                         ServiceAccessType::EXPLICIT_ACCESS),
                     HistoryServiceFactory::GetForProfile(
                         profile,
                         ServiceAccessType::EXPLICIT_ACCESS)) {
  controller_ = std::make_unique<AutocompleteController>(
      std::make_unique<ChromeAutocompleteProviderClient>(profile),
      provider_types, /*is_cros_launcher=*/true),
  controller_->AddObserver(this);
}

OmniboxProvider::~OmniboxProvider() {}

void OmniboxProvider::Start(const std::u16string& query) {
  last_query_ = query;
  last_tokenized_query_.emplace(query, TokenizedString::Mode::kCamelCase);

  controller_->Stop(false);
  query_finished_ = false;
  // The new page classification value(CHROMEOS_APP_LIST) is introduced
  // to differentiate the suggest requests initiated by ChromeOS app_list from
  // the ones by Chrome omnibox.
  input_ =
      AutocompleteInput(query, metrics::OmniboxEventProto::CHROMEOS_APP_LIST,
                        ChromeAutocompleteSchemeClassifier(profile_));

  query_start_time_ = base::TimeTicks::Now();
  controller_->Start(input_);
}

void OmniboxProvider::StopQuery() {
  last_query_.clear();
  last_tokenized_query_.reset();
  query_finished_ = false;

  controller_->Stop(true);
}

ash::AppListSearchResultType OmniboxProvider::ResultType() const {
  return ash::AppListSearchResultType::kOmnibox;
}

void OmniboxProvider::PopulateFromACResult(const AutocompleteResult& result) {
  SearchProvider::Results new_results;
  new_results.reserve(result.size());

  std::vector<std::unique_ptr<OmniboxResult>> list_results;
  list_results.reserve(result.size());

  for (const AutocompleteMatch& match : result) {
    // Do not return a match in any of these cases:
    // - The URL is invalid.
    // - The URL points to Drive Web and is not an open tab. The Drive search
    //   provider surfaces Drive results.
    // - The URL points to a local file. The Local file search provider handles
    //   local file results, even if they've been opened in the browser.
    const bool is_drive = IsDriveUrl(match.destination_url) &&
                          match.type != AutocompleteMatchType::OPEN_TAB;
    if (!match.destination_url.is_valid() || is_drive ||
        match.destination_url.SchemeIsFile()) {
      continue;
    }

    if (match.type == AutocompleteMatchType::OPEN_TAB) {
      // Filters out open tab results if web in disabled in launcher search
      // controls.
      if (ash::features::IsLauncherSearchControlEnabled() &&
          !IsControlCategoryEnabled(profile_, ControlCategory::kWeb)) {
        continue;
      }
      DCHECK(last_tokenized_query_.has_value());
      new_results.emplace_back(std::make_unique<OpenTabResult>(
          profile_, list_controller_,
          crosapi::CreateResult(
              match, controller_.get(), &favicon_cache_,
              BookmarkModelFactory::GetForBrowserContext(profile_), input_),
          last_tokenized_query_.value()));
    } else if (!IsAnswer(match)) {
      // Filters out omnibox results if web in disabled in launcher search
      // controls.
      if (ash::features::IsLauncherSearchControlEnabled() &&
          !IsControlCategoryEnabled(profile_, ControlCategory::kWeb)) {
        continue;
      }
      list_results.emplace_back(std::make_unique<OmniboxResult>(
          profile_, list_controller_,
          crosapi::CreateResult(
              match, controller_.get(), &favicon_cache_,
              BookmarkModelFactory::GetForBrowserContext(profile_), input_),
          last_query_));
    } else {
      new_results.emplace_back(std::make_unique<OmniboxAnswerResult>(
          profile_, list_controller_,
          crosapi::CreateAnswerResult(match, controller_.get(), last_query_,
                                      input_),
          last_query_));
    }
  }

  // Deduplicate the list results and then move-concatenate it into new_results.
  RemoveDuplicateResults(list_results);
  std::move(list_results.begin(), list_results.end(),
            std::back_inserter(new_results));

  if (controller_->done() && !query_finished_) {
    query_finished_ = true;
    SwapResults(&new_results);
  }
}

void OmniboxProvider::OnResultChanged(AutocompleteController* controller,
                                      bool default_match_changed) {
  DCHECK(controller == controller_.get());

  // Record the query latency.
  base::TimeDelta query_latency = base::TimeTicks::Now() - query_start_time_;
  UMA_HISTOGRAM_TIMES("Apps.AppList.OmniboxProvider.QueryTime", query_latency);

  PopulateFromACResult(controller_->result());
}

}  // namespace app_list