910e62b5创建于 1月15日历史提交
// Copyright 2025 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/auxiliary_search/fetch_and_rank_helper.h"

#include <vector>

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/auxiliary_search/auxiliary_search_provider.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.h"
#include "components/visited_url_ranking/public/features.h"
#include "components/visited_url_ranking/public/fetch_options.h"
#include "components/visited_url_ranking/public/url_visit.h"
#include "components/visited_url_ranking/public/url_visit_util.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "url/android/gurl_android.h"
#include "url/url_constants.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/browser/auxiliary_search/jni_headers/FetchAndRankHelper_jni.h"

using visited_url_ranking::Config;
using visited_url_ranking::Fetcher;
using visited_url_ranking::FetchOptions;
using visited_url_ranking::ResultStatus;
using visited_url_ranking::URLVisitAggregate;
using visited_url_ranking::URLVisitAggregatesTransformType;
using visited_url_ranking::URLVisitsMetadata;
using visited_url_ranking::VisitedURLRankingService;
using visited_url_ranking::VisitedURLRankingServiceFactory;

namespace {
// Must match Java Tab.INVALID_TAB_ID.
static constexpr int kInvalidTabId = -1;

// 1 day in hours.
const int kHistoryAgeThresholdHoursDefaultValue = 24;
// 7 days in hours.
const int kTabAgeThresholdHoursDefaultValue = 168;

// Get the default age limit for the `url_type`.
base::TimeDelta GetDefaultAgeLimit(URLVisitAggregate::URLType url_type) {
  switch (url_type) {
    case URLVisitAggregate::URLType::kActiveLocalTab:
      return base::Hours(kTabAgeThresholdHoursDefaultValue);
    case URLVisitAggregate::URLType::kCCTVisit:
      return base::Hours(kHistoryAgeThresholdHoursDefaultValue);
    default:
      return base::TimeDelta();
  }
}

// Get the default visit duration limit for the `url_type`.
std::optional<base::TimeDelta> GetDefaultVisitDurationLimit(
    URLVisitAggregate::URLType url_type,
    bool skip_visit_duration_limit = false) {
  switch (url_type) {
    case URLVisitAggregate::URLType::kCCTVisit:
      // Needs to skip visit_duration check in
      // HistoryURLVisitDataFetcher#OnGotAnnotatedVisits when fetching an open
      // CCT whose visit_duration is 0. The visit duration will be set to the
      // history database once the CCT is closed.
      if (skip_visit_duration_limit) {
        return std::nullopt;
      }

      return base::Seconds(
          chrome::android::kAppIntegrationCCTVisitDurationLimitSecParam.Get());
    default:
      return std::nullopt;
  }
}

// Returns the maximum count of entries to donate.
int GetMaxDonationCount() {
  return chrome::android::kAppIntegrationMaxDonationCountParam.Get();
}

FetchOptions CreateFetchOptionsForTabDonation(
    const URLVisitAggregate::URLTypeSet& result_sources,
    const std::optional<GURL>& custom_tab_url,
    std::optional<base::Time> begin_time) {
  std::vector<URLVisitAggregatesTransformType> transforms{
      URLVisitAggregatesTransformType::kRecencyFilter,
      URLVisitAggregatesTransformType::kDefaultAppUrlFilter,
      URLVisitAggregatesTransformType::kHistoryBrowserTypeFilter,
  };

  if (base::FeatureList::IsEnabled(
          visited_url_ranking::features::
              kVisitedURLRankingHistoryVisibilityScoreFilter)) {
    transforms.push_back(
        URLVisitAggregatesTransformType::kHistoryVisibilityScoreFilter);
  }

  std::map<Fetcher, visited_url_ranking::FetchOptions::FetchSources>
      fetcher_sources;
  // Always useful for signals.
  fetcher_sources.emplace(Fetcher::kHistory,
                          visited_url_ranking::FetchOptions::kOriginSources);

  // If a URL of Custom Tab is provided, the fetcher only fetches history, not
  // open Tabs.
  if (custom_tab_url == std::nullopt) {
    fetcher_sources.emplace(
        Fetcher::kTabModel,
        visited_url_ranking::FetchOptions::FetchSources(
            {visited_url_ranking::URLVisit::Source::kLocal}));
  }

  // Sets the query duration to match the age limit for the local Tabs. It
  // allows getting the sensitivity scores of all qualified local Tabs.
  int query_duration = base::GetFieldTrialParamByFeatureAsInt(
      visited_url_ranking::features::kVisitedURLRankingService,
      visited_url_ranking::features::
          kVisitedURLRankingFetchDurationInHoursParam,
      kTabAgeThresholdHoursDefaultValue);
  std::map<URLVisitAggregate::URLType,
           visited_url_ranking::FetchOptions::ResultOption>
      result_map;
  for (URLVisitAggregate::URLType type : result_sources) {
    result_map[type] = visited_url_ranking::FetchOptions::ResultOption{
        .age_limit = GetDefaultAgeLimit(type),
        .visit_duration_limit =
            GetDefaultVisitDurationLimit(type, begin_time != std::nullopt)};
  }

  // Adjusts the begin time.
  base::Time new_begin_time =
      std::max(base::Time::Now() - base::Hours(query_duration),
               begin_time.value_or(base::Time()));

  return FetchOptions(std::move(result_map), std::move(fetcher_sources),
                      new_begin_time, std::move(transforms),
                      GetMaxDonationCount());
}

FetchOptions CreateFetchOptions(const std::optional<GURL>& custom_tab_url,
                                std::optional<base::Time> begin_time) {
  URLVisitAggregate::URLTypeSet expected_types;
  if (custom_tab_url == std::nullopt) {
    expected_types = {URLVisitAggregate::URLType::kActiveLocalTab,
                      URLVisitAggregate::URLType::kCCTVisit};
  } else {
    expected_types = {URLVisitAggregate::URLType::kCCTVisit};
  }
  return CreateFetchOptionsForTabDonation(expected_types, custom_tab_url,
                                          begin_time);
}

}  // namespace

FetchAndRankHelper::FetchAndRankHelper(
    VisitedURLRankingService* ranking_service,
    FetchResultCallback entries_callback,
    const std::optional<GURL>& custom_tab_url,
    std::optional<base::Time> begin_time)
    : ranking_service_(ranking_service),
      entries_callback_(std::move(entries_callback)),
      custom_tab_url_(custom_tab_url),
      fetch_options_(CreateFetchOptions(custom_tab_url, begin_time)),
      config_({.key = visited_url_ranking::kTabResumptionRankerKey}) {}

void FetchAndRankHelper::StartFetching() {
  ranking_service_->FetchURLVisitAggregates(
      fetch_options_,
      base::BindOnce(&FetchAndRankHelper::OnFetched, base::RetainedRef(this)));
}

FetchAndRankHelper::~FetchAndRankHelper() = default;

void FetchAndRankHelper::OnFetched(ResultStatus status,
                                   URLVisitsMetadata url_visits_metadata,
                                   std::vector<URLVisitAggregate> aggregates) {
  if (status != ResultStatus::kSuccess) {
    std::vector<jni_zero::ScopedJavaLocalRef<jobject>> entries;
    std::move(entries_callback_).Run(std::move(entries), url_visits_metadata);
    return;
  }

  ranking_service_->RankURLVisitAggregates(
      config_, std::move(aggregates),
      base::BindOnce(&FetchAndRankHelper::OnRanked, base::RetainedRef(this),
                     std::move(url_visits_metadata)));
}

void FetchAndRankHelper::OnRanked(URLVisitsMetadata url_visits_metadata,
                                  ResultStatus status,
                                  std::vector<URLVisitAggregate> aggregates) {
  JNIEnv* env = base::android::AttachCurrentThread();
  std::vector<jni_zero::ScopedJavaLocalRef<jobject>> entries;
  if (status != ResultStatus::kSuccess) {
    std::move(entries_callback_).Run(std::move(entries), url_visits_metadata);
    return;
  }

  for (const URLVisitAggregate& aggregate : aggregates) {
    if (aggregate.fetcher_data_map.empty()) {
      continue;
    }
    // TODO(crbug.com/337858147): Choose representative member. For now, just
    // take the first one.
    const auto& fetcher_entry = *aggregate.fetcher_data_map.begin();
    std::visit(
        absl::Overload{
            [&](const URLVisitAggregate::TabData& tab_data) {
              bool is_local_tab =
                  (tab_data.last_active_tab.id != kInvalidTabId);
              if (!is_local_tab) {
                return;
              }

              entries.push_back(Java_FetchAndRankHelper_addDataEntry(
                  env,

                  static_cast<int>(AuxiliarySearchEntryType::kTab),
                  url::GURLAndroid::FromNativeGURL(
                      env, tab_data.last_active_tab.visit.url),
                  base::android::ConvertUTF16ToJavaString(
                      env, tab_data.last_active_tab.visit.title),
                  tab_data.last_active.InMillisecondsSinceUnixEpoch(),
                  tab_data.last_active_tab.id, /* appId= */ nullptr,
                  kInvalidTabId));
            },
            [&](const URLVisitAggregate::HistoryData& history_data) {
              bool is_custom_tab =
                  history_data.last_visited.context_annotations.on_visit
                          .browser_type == history::VisitContextAnnotations::
                                               BrowserType::kCustomTab ||
                  history_data.last_app_id != std::nullopt ||
                  (custom_tab_url_ != std::nullopt &&
                   custom_tab_url_.value() ==
                       history_data.last_visited.url_row.url());
              if (!is_custom_tab) {
                return;
              }

              entries.push_back(Java_FetchAndRankHelper_addDataEntry(
                  env,

                  static_cast<int>(AuxiliarySearchEntryType::kCustomTab),
                  url::GURLAndroid::FromNativeGURL(
                      env, history_data.last_visited.url_row.url()),
                  base::android::ConvertUTF16ToJavaString(
                      env, history_data.last_visited.url_row.title()),
                  history_data.last_visited.visit_row.visit_time
                      .InMillisecondsSinceUnixEpoch(),
                  kInvalidTabId,
                  history_data.last_app_id
                      ? base::android::ConvertUTF8ToJavaString(
                            env, *history_data.last_app_id)
                      : base::android::ConvertUTF8ToJavaString(env,
                                                               std::string()),
                  std::abs(static_cast<int>(base::Hash(aggregate.url_key)))));
            }},
        fetcher_entry.second);
  }

  std::move(entries_callback_).Run(std::move(entries), url_visits_metadata);
}

DEFINE_JNI(FetchAndRankHelper)