chromium/chrome/browser/history_clusters/history_clusters_bridge.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/history_clusters/history_clusters_bridge.h"

#include <utility>

#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 "chrome/browser/history_clusters/history_clusters_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/history/core/browser/history_types.h"
#include "url/android/gurl_android.h"

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

using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;

namespace {
const char kHistoryClustersBridgeKey[] = "history-clusters-bridge";
}

namespace history_clusters {

HistoryClustersBridge::HistoryClustersBridge(
    JNIEnv* env,
    HistoryClustersService* history_clusters_service)
    : history_clusters_service_(history_clusters_service) {
  ScopedJavaLocalRef<jobject> j_history_clusters_bridge =
      Java_HistoryClustersBridge_create(env, reinterpret_cast<jlong>(this));
  java_ref_.Reset(j_history_clusters_bridge);
}

HistoryClustersBridge::~HistoryClustersBridge() = default;

void HistoryClustersBridge::ClustersQueryDone(
    JNIEnv* env,
    const JavaRef<jobject>& j_this,
    const JavaRef<jobject>& j_callback,
    const std::string& query,
    std::vector<history::Cluster> clusters,
    bool can_load_more,
    bool is_continuation) {
  std::vector<ScopedJavaLocalRef<jobject>> j_clusters;
  for (const history::Cluster& cluster : clusters) {
    std::vector<ScopedJavaLocalRef<jobject>> cluster_visits;
    for (const history::ClusterVisit& visit : cluster.visits) {
      std::vector<int> title_match_starts;
      std::vector<int> title_match_ends;
      for (const auto& match : visit.title_match_positions) {
        title_match_starts.push_back(match.first);
        title_match_ends.push_back(match.second);
      }

      std::vector<int> url_match_starts;
      std::vector<int> url_match_ends;
      for (const auto& match : visit.url_for_display_match_positions) {
        url_match_starts.push_back(match.first);
        url_match_ends.push_back(match.second);
      }

      std::vector<int64_t> duplicated_visit_timestamps;
      std::vector<ScopedJavaLocalRef<jobject>> duplicated_visit_urls;
      for (const auto& duplicate : visit.duplicate_visits) {
        duplicated_visit_timestamps.push_back(
            duplicate.visit_time.ToInternalValue());
        duplicated_visit_urls.push_back(
            url::GURLAndroid::FromNativeGURL(env, duplicate.url));
      }

      const ScopedJavaLocalRef<jobject>& j_cluster_visit =
          Java_HistoryClustersBridge_buildClusterVisit(
              env, visit.score,
              url::GURLAndroid::FromNativeGURL(env, visit.normalized_url),
              base::android::ConvertUTF16ToJavaString(env,
                                                      visit.url_for_display),
              base::android::ConvertUTF16ToJavaString(
                  env, visit.annotated_visit.url_row.title()),
              base::android::ToJavaIntArray(env, title_match_starts),
              base::android::ToJavaIntArray(env, title_match_ends),
              base::android::ToJavaIntArray(env, url_match_starts),
              base::android::ToJavaIntArray(env, url_match_ends),
              url::GURLAndroid::FromNativeGURL(
                  env, visit.annotated_visit.url_row.url()),
              visit.annotated_visit.visit_row.visit_time.ToInternalValue(),
              base::android::ToJavaLongArray(env, duplicated_visit_timestamps),
              duplicated_visit_urls);
      cluster_visits.push_back(j_cluster_visit);
    }
    base::Time visit_time;
    if (!cluster.visits.empty())
      visit_time = cluster.visits[0].annotated_visit.visit_row.visit_time;
    ScopedJavaLocalRef<jclass> cluster_visit_type = base::android::GetClass(
        env, "org/chromium/chrome/browser/history_clusters/ClusterVisit");
    std::u16string label = cluster.label.value_or(u"no_label");
    std::u16string raw_label = cluster.raw_label.value_or(u"no_label");

    // Passing objects more complex than primitives requires extra JNI hops, so
    // we destructure matches into arrays which can be passed in one hop.
    std::vector<int> label_match_starts;
    std::vector<int> label_match_ends;
    for (const auto& match : cluster.label_match_positions) {
      label_match_starts.push_back(match.first);
      label_match_ends.push_back(match.second);
    }

    const ScopedJavaLocalRef<jobject>& j_cluster =
        Java_HistoryClustersBridge_buildCluster(
            env,
            base::android::ToTypedJavaArrayOfObjects(env, cluster_visits,
                                                     cluster_visit_type),
            base::android::ConvertUTF16ToJavaString(env, label),
            base::android::ConvertUTF16ToJavaString(env, raw_label),
            base::android::ToJavaIntArray(env, label_match_starts),
            base::android::ToJavaIntArray(env, label_match_ends),
            visit_time.InMillisecondsSinceUnixEpoch(),
            base::android::ToJavaArrayOfStrings(env, cluster.related_searches));
    j_clusters.push_back(j_cluster);
  }
  ScopedJavaLocalRef<jclass> cluster_type = base::android::GetClass(
      env, "org/chromium/chrome/browser/history_clusters/HistoryCluster");
  std::vector<std::u16string> unique_raw_labels;
  std::vector<int> label_counts;
  if (query_clusters_state_->query().empty()) {
    for (const auto& label_entry :
         query_clusters_state_->raw_label_counts_so_far()) {
      unique_raw_labels.push_back(label_entry.first);
      label_counts.push_back(label_entry.second);
    }
  }

  const ScopedJavaLocalRef<jobject>& j_result =
      Java_HistoryClustersBridge_buildClusterResult(
          env,
          base::android::ToTypedJavaArrayOfObjects(env, j_clusters,
                                                   cluster_type),
          base::android::ToJavaArrayOfStrings(env, unique_raw_labels),
          base::android::ToJavaIntArray(env, label_counts),
          base::android::ConvertUTF8ToJavaString(env, query), can_load_more,
          is_continuation);
  base::android::RunObjectCallbackAndroid(j_callback, j_result);
}

void HistoryClustersBridge::Destroy(JNIEnv* j_env) {
  delete this;
}

}  // namespace history_clusters