// 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